読者です 読者をやめる 読者になる 読者になる

paiza開発日誌

paiza(https://paiza.jp)の開発者が開発の事、プログラミングネタ、ITエンジニアの転職などについて書いています。

コマンドラインツールを自動生成できるPython Fireと他のライブラリ比べてみた

プログラミング学習 プログラミング初心者 Webサービス紹介

f:id:paiza:20170310142402j:plain
Photo by Jayphen
秋山です。

先日「Python Fire」という、Pythonコマンドラインツールを自動生成できるライブラリが発表されました。

どのへんが便利なのか、実際に使ってみながら解説をしていきますので、気になってた人の参考になればと思います。

Googleリポジトリに出ているのでGoogleの公式プロダクト?と思いきや、最後に「This is not an official Google product.」の表記があるのでGoogle公式ではないようですね。Googleの中の人が作った非公式ライブラリということでしょうかね。

github.com

Python Fire使ってみた

そもそもPythonには、コマンドラインからコマンドを受け付ける組み込みのライブラリがあります。他にもbakerとか、clickとか、既にコマンドラインツールを作る用の補助ライブラリもいくつかあります。

今回は「コマンドを叩けば流行りのbitcoinの価格をチェックできるようなツールを作る」ことを目的に、Python Fireと各ライブラリを試しながら比較してみたいと思います。(ちなみに私は普段はよくbakerか組み込みライブラリを使っています)

bitFlyerさんが公開されている、簡単に使えるtickerを取得するpublic apiを利用させていただきます。(詳しくはこちら

ちなみにbitFlyerさんの求人情報はこちら。
株式会社bitFlyer エンジニア求人 | ITプログラマ・エンジニア向け転職・就活・学習サービスのpaiza


ではまず単純に、価格を取得するpythonのコードを書いてみます。

def bitflyer_getticker(product_code, is_json):                                                      
    end_point = "https://api.bitflyer.jp/v1/getticker"                                              
    r = requests.get(end_point, params={'product_code' : product_code})                             
    r_json = json.loads(r.text)                                                                     
    if product_code == None:                                                                        
        print('BTC_JPY')                                                                            
    else:                                                                                           
        print(product_code)                                                                         
                                                                                                    
    if is_json:                                                                                     
        print(r_json)                                                                               
    else:                                                                                           
        print("bid:{}".format(r_json['best_bid']))                                                  
        print("ask{}".format(r_json['best_ask'])) 

product_codeは、BTC_JPY(bitcoinと円) やBTC_ETC(bitcoinとetherium)を指定することができます。(詳細はbitFlyerさんのAPIのドキュメントを参照してください)

is_jsonがtrueなら、jsonをprintするようにしています。これ以降はこのメソッドを流用して、各ライブラリでコマンドラインツール化をしてみましょう。

コードの再利用性や他のAPIへの対応はひとまず考えずに、tickerの取得のみをします。


まずはPython標準の argparse を使ってみましょう。

def bitflyer_getticker(product_code, is_json):                                                      
    end_point = "https://api.bitflyer.jp/v1/getticker"                                              
    r = requests.get(end_point, params={'product_code' : product_code})                             
    r_json = json.loads(r.text)                                                                     
    if product_code == None:                                                                        
        print('BTC_JPY')                                                                            
    else:                                                                                           
        print(product_code)                                                                         
                                                                                                    
    if is_json:                                                                                     
        print(r_json)                                                                               
    else:                                                                                           
        print("bid:{}".format(r_json['best_bid']))                                                  
        print("ask{}".format(r_json['best_ask']))                                                   
                                                                                                    
import argparse                                                                                     
                                                                                                    
parser = argparse.ArgumentParser(description='bitflyer api getticker')                              
parser.add_argument('-p', '--p', type=str, help=u'BTC_JPY or BTC_ETH')                              
parser.add_argument('-j', '--json', action='store_true', help=u"return json")                        
                                                                                                    
args = parser.parse_args()                                                                          
bitflyer_getticker(product_code=args.p, is_json=args.json)                                          

上記のコードでは、-pオプションでBTC_JPYとかETH_BTCを文字列で指定できるようにして、--jsonオプションでjsonを返すようにしてみました。

python bitflyer.py -p ETH_BTC --json

とすると、ETH_BTCを指定して、jsonを出力となります。各コマンドを省略するとBTC_JPYのaskとbidのみ出力します。

とりあえず、使えるようにはなりましたね。


では次に、bakerを使ってみましょう。

bakerはimportして@baker.commandするだけで、なんとな~くコマンドラインツールっぽくしてくれます。

import requests, json, baker                                                                        
                                                                                                    
@baker.command(params={"product_code": "'BTC_JPY or BTC_ETH", "is_json": "return json"})                                                                                      
def bitflyer_getticker(product_code='BTC_JPY', is_json=False):                                      
    """bitflyer api getticker                                                                  
    """                                                                                             
    end_point = "https://api.bitflyer.jp/v1/getticker"                                              
    r = requests.get(end_point, params={'product_code' : product_code})                             
    r_json = json.loads(r.text)                                                                     
    if product_code == None:                                                                        
        print('BTC_JPY')                                                                            
    else:                                                                                           
        print(product_code)                                                                         
                                                                                                    
    if is_json:                                                                                     
        print(r_json)                                                                               
    else:                                                                                           
        print("bid:{}".format(r_json['best_bid']))                                                  
        print("ask{}".format(r_json['best_ask']))                                                   
                                                                                                    
baker.run() 

これで、メソッド名をサブコマンドとしてコマンドラインツールっぽく使えるようになります。

python bitflyer_baker.py bitflyer_getticker --product_code ETH_BTC --is_json

ETH_BTCを指定し、jsonを出力します。各コマンドを省略すると、BTC_JPYのaskとbidのみ出力します。

メソッド名がサブコマンドとされたり、–helpとするとコメントの部分が出力されたり、paramsで設定したオプションの詳細などが以下のように表示されます。

python bitflyer_baker.py bitflyer_getticker --help
Usage: bitflyer_baker.py bitflyer_getticker [<product_code>] [<is_json>]

bitflyer api getticker

Options:

   --product_code  'BTC_JPY or BTC_ETH
   --is_json       return json

ざっくりコマンドツールを作る場合はこれで十分かもしれません。


続いてclickでやってみます。

import requests, json, click                                                                        
                                                                                                    
@click.command()                                                                                    
@click.option('--product_code', '-p', default='BTC_JPY', help=u'BTC_JPY or BTC_ETH')                
@click.option('--is_json/--no-is_json', default=False, help=u"return json")                         
def bitflyer_getticker(product_code='BTC_JPY', is_json=False):                                      
    end_point = "https://api.bitflyer.jp/v1/getticker"                                              
    r = requests.get(end_point, params={'product_code' : product_code})                             
    r_json = json.loads(r.text)                                                                     
    if product_code == None:                                                                        
        print('BTC_JPY')                                                                            
    else:                                                                                           
        print(product_code)                                                                         
                                                                                                    
    if is_json:                                                                                     
        print(r_json)                                                                               
    else:                                                                                           
        print("bid:{}".format(r_json['best_bid']))                                                  
        print("ask{}".format(r_json['best_ask']))                                                   
                                                                                                    
if __name__ == "__main__":                                                                          
    bitflyer_getticker()
python  bitflyer_click.py -p ETH_BTC --is_json

とすると、ETH_BTCを指定しjsonを出力となります。各コマンドを省略するとBTC_JPYのaskとbidのみ出力します。

こちらも–helpオプションで表示されるメッセージが、オプションなどからいい感じに作ってもらえます。こんな感じで表示されます。

python  bitflyer_click.py --help
Usage: bitflyer_click.py [OPTIONS]

Options:
  -p, --product_code TEXT   BTC_JPY or BTC_ETH
  --is_json / --no-is_json  return json
  --help                    Show this message and exit.


では最後に、Python Fireで作ってみます。

import requests, json, fire                                                                         
                                                                                                    
def bitflyer_getticker(product_code='BTC_JPY', is_json=False):                                      
    end_point = "https://api.bitflyer.jp/v1/getticker"                                              
    r = requests.get(end_point, params={'product_code' : product_code})                             
    r_json = json.loads(r.text)                                                                     
    if product_code == None:                                                                        
        print('BTC_JPY')                                                                            
    else:                                                                                           
        print(product_code)                                                                         
                                                                                                    
    if is_json:                                                                                     
        print(r_json)                                                                               
    else:                                                                                           
        print("bid:{}".format(r_json['best_bid']))                                                  
        print("ask{}".format(r_json['best_ask']))                                                   
                                                                                                    
if __name__ == "__main__":                                                                          
    fire.Fire(bitflyer_getticker)

ちなみにクラスをつっこむなら以下のようにします。

import requests, json, fire                                                                                                                                                              
class Bitflyer():                                                                                   
    def __init__(self, end_point="https://api.bitflyer.jp/v1/getticker"):                           
        self.end_point = end_point                                                                  
                                                                                                    
    def getticker(self, product_code='BTC_JPY', is_json=False):                                     
        r = requests.get(self.end_point, params={'product_code' : product_code})                    
        r_json = json.loads(r.text)                                                                 
        if product_code == None:                                                                    
            print('BTC_JPY')                                                                        
        else:                                                                                       
            print(product_code)                                                                     
                                                                                                    
        if is_json:                                                                                 
            print(r_json)                                                                           
        else:                                                                                       
            print("bid:{}".format(r_json['best_bid']))                                              
            print("ask{}".format(r_json['best_ask']))                                               
                                                                                                    
if __name__ == "__main__":                                                                          
    fire.Fire(Bitflyer)

fire.Fire()にクラスを投げ込むだけでコマンドラインツール化してくれます。

コマンド上では、クラスを突っ込んだ場合はメソッド名をサブコマンドとして、このように引数を順に与えている感じになります。

python bitflyer_fire.py getticker ETH_BTC True

引数の順番を変えたい場合などは -- をつけて

python bitflyer_fire.py getticker --product_code=ETH_BTC --is_json=True

というようなコマンドで実行できます。

ヘルプは以下のようになります。

python bitflyer_fire.py -- --help
Type:        type
String form: <class '__main__.Bitflyer'>
File:        ~/python_commands/bitflyer_fire.py
Line:        4

Usage:       bitflyer_fire.py [END_POINT]
             bitflyer_fire.py [--end-point END_POINT]


さらにPython Fire用の特殊なオプションがいくつかあります。

それらオプションは上記で説明したコマンドの末尾に -- で区切り --verbose などとつけることが出来ます。

上記のクラスをfireに渡した場合を例に試してみます。

--interactive

こちらはインタラクティブモードで実行、IPython上で実行されます。

python bitflyer_fire.py -- --interactive

とすると、以下のようにREPLが起動します。

Fire is starting a Python REPL with the following objects:
Modules: fire, json, requests
Objects: Bitflyer, bitflyer_fire.py, component, result, trace

Python 3.6.0 (default, Jan  5 2017, 13:12:02)
Type "copyright", "credits" or "license" for more information.

IPython 5.1.0 -- An enhanced Interactive Python.
?         -> Introduction and overview of IPython's features.
%quickref -> Quick reference.
help      -> Python's own help system.
object?   -> Details about 'object', use 'object??' for extra details.

In [1]: Bitflyer().getticker()
BTC_JPY
bid:138251.0
ask138407.0

In [2]: 

REPL上でBitflyer().getticker()とするとコマンド実行と同じように動作します。

--completion

こちらのコマンドはbash用のコマンドの補完が出来るようになるスクリプトを吐きます。

.bashrcなどに貼り付け、パスが通っており、実行権限を与えると動作するようになります。


他にも以下のようなオプションがいくつかありますが、特に有用なのは上2つかと思います。

--help
--trace
--separator
--verbose

ちなみにこれらオプションの詳細は、以下のページに記載されています。
python-fire/using-cli.md at master · google/python-fire · GitHub

■まとめ

今の目的だと、Pythonデフォルト以外は割とどれも似たような感じですね。

  • bakerは、メソッドをつっこむだけで引数などから勝手にヘルプを作ってもらえます。何も考えずに作れるなら個人的にはこれが好きです。
  • clickは、デコレータでoptionなどを設定し、そこからヘルプを作ります。少し面倒ですが、 --is_json/--no-is_json といった boolean を --no〜 とかで False を設定できるようにしたりと、柔軟な設定が可能です。
  • Python Fireは、クラスやメソッドを投げ込む感じなので、既存のものをコマンド化するという点ではbakerに近いのかなと思います。ヘルプなどの自動生成はbakerに似てますね。

それぞれ色々な機能がありそうですが今回試した感じでは、何も考えずに既存のクラスやメソッドを入れたい!みたいな使い方をするのであれば、Python Fireは良さそうですね。

help上に String form: などPython上の情報なども出るので、Pythonを分かっている人がそのまま投げ込んで便利に使う用なのかな?と感じました。
またbash用のスクリプトを吐く、インタラクティブモードがあるなど、便利機能も組み込まれているので上手く使えれば捗りそうです。


なお、Pythonを基礎から学びたいプログラミング初心者の方はこちらをどうぞ。
paiza.jp

Pythonの講座も公開中!プログラミングが動画で学べるレッスン


paizaは、プログラミング未経験者・初心者向け学習サービス「paizaラーニングを、新サービスとして独立オープンいたしました。

今回記事の中で使用しているPythonの入門講座も好評公開中です。ぜひごらんください!

↓詳しくはこちら
paiza.jp

そして、paizaでは、Webサービス開発企業などで求められるコーディング力や、テストケースを想定する力などが問われるプログラミングスキルチェック問題も提供しています。
paiza.jp
スキルチェックに挑戦した人は、その結果によってS・A・B・C・D・Eの6段階のランクを取得できます。必要なスキルランクを取得すれば、書類選考なしで企業の求人に応募することも可能です。「自分のプログラミングスキルを客観的に知りたい」「スキルを使って転職したい」という方は、ぜひチャレンジしてみてください。

ITプログラマ・エンジニア向け転職・就活・学習サービスのpaiza

プログラミング入門講座|paizaラーニング

PHP入門編Ruby入門編Python入門編Java入門編JavaScript入門編C言語入門編C#入門編アルゴリズム入門編