爆速python-fire
Kazuki Moriyama (森山 和樹)
python-fire/guide.md at master · google/python-fire · GitHub
- pythonでcliをクソ簡単に作成できるgoogle製ライブラリ
- argparserとか使ってたのがアホらしくなるほど簡単
基本
関数をfireに渡して呼べば終わり。
import fire
def hello(name="World"):
return "Hello %s!" % name
if __name__ == '__main__':
fire.Fire(hello)
上のコードは下のようにcliコマンドに早変わり。
python hello.py # Hello World!
python hello.py --name=David # Hello David!
クラスを渡すとメソッドをcliから呼べる。
import fire
class Calculator(object):
"""A simple calculator class."""
def double(self, number):
return 2 * number
if __name__ == '__main__':
fire.Fire(Calculator)
python calculator.py double 10 # 20
python calculator.py double --number=15 # 30
複数のコマンドを作成
まずcliコマンドがどうなるか示すと以下のような感じ。
$ python example.py add 10 20
30
$ python example.py multiply 10 20
200
これを実現する方法は以下のように複数ある。
シンプルにfireだけを呼ぶ
- ファイル内の関数がすべてcliコマンド化する
- 簡単だけどいらない関数があってもコマンドになってしまう
import fire
def add(x, y):
return x + y
def multiply(x, y):
return x * y
if __name__ == '__main__':
fire.Fire()
- ファイル内に変数があっても、それもコマンドで呼び出せる
import fire
english = 'Hello World'
spanish = 'Hola Mundo'
fire.Fire()
$ python example.py english
Hello World
$ python example.py spanish
Hola Mundo
dictを渡してfire
- ファイル内から必要な関数を選択してcliコマンドにできる
import fire
def add(x, y):
return x + y
def multiply(x, y):
return x * y
if __name__ == '__main__':
fire.Fire({
'add': add,
'multiply': multiply,
})
コマンド専用クラスのインスタンスを渡す
import fire
class Calculator(object):
def add(self, x, y):
return x + y
def multiply(self, x, y):
return x * y
if __name__ == '__main__':
calculator = Calculator()
fire.Fire(calculator)
コマンド専用のクラス自身を渡す
- インスタンス化する際に渡すインスタンス変数までcliの引数にできるためインスタンスを渡すより便利
import fire
class BrokenCalculator(object):
def __init__(self, offset=1):
self._offset = offset
def add(self, x, y):
return x + y + self._offset
def multiply(self, x, y):
return x * y + self._offset
if __name__ == '__main__':
fire.Fire(BrokenCalculator)
$ python example.py add 10 20 --offset=0
30
$ python example.py multiply 10 20 --offset=0
200
コマンドのグループ化
- 複数のコマンドクラスを作り、インスタンス変数を介してネストさせると複雑なコマンドが実現できる
class IngestionStage(object):
def run(self):
return 'Ingesting! Nom nom nom...'
class DigestionStage(object):
def run(self, volume=1):
return ' '.join(['Burp!'] * volume)
def status(self):
return 'Satiated.'
class Pipeline(object):
def __init__(self):
self.ingestion = IngestionStage()
self.digestion = DigestionStage()
def run(self):
self.ingestion.run()
self.digestion.run()
if __name__ == '__main__':
fire.Fire(Pipeline)
$ python example.py run
Ingesting! Nom nom nom...
Burp!
$ python example.py ingestion run
Ingesting! Nom nom nom...
$ python example.py digestion run
Burp!
$ python example.py digestion status
Satiated.
クラスのプロパティにアクセス
- fireにクラスを渡すとメソッドじゃなくインスタンス変数なども呼べる
from airports import airports
import fire
class Airport(object):
def __init__(self, code):
self.code = code
self.name = dict(airports).get(self.code)
self.city = self.name.split(',')[0] if self.name else None
if __name__ == '__main__':
fire.Fire(Airport)
$ python example.py --code=JFK code
JFK
$ python example.py --code=SJC name
San Jose-Sunnyvale-Santa Clara, CA - Norman Y. Mineta San Jose International (SJC)
$ python example.py --code=ALB city
Albany-Schenectady-Troy
コマンドの戻り値に関数を適用する
- 戻り値のにたいしてメソッドを呼ぶようにコマンドをチェインできる
- 下の例だと
str.upper
メソッドが
呼ばれている
# コード自体は上のAirportの例
$ python example.py --code=ALB city upper
ALBANY-SCHENECTADY-TROY
- コマンドクラスのメソッドがすべて自身を返すようにすると次々にメソッドをチェインできるから便利
import fire
class BinaryCanvas(object):
"""A canvas with which to make binary art, one bit at a time."""
def __init__(self, size=10):
self.pixels = [[0] * size for _ in range(size)]
self._size = size
self._row = 0 # The row of the cursor.
self._col = 0 # The column of the cursor.
def __str__(self):
return '\n'.join(' '.join(str(pixel) for pixel in row) for row in self.pixels)
def show(self):
print(self)
return self
def move(self, row, col):
self._row = row % self._size
self._col = col % self._size
return self
def on(self):
return self.set(1)
def off(self):
return self.set(0)
def set(self, value):
self.pixels[self._row][self._col] = value
return self
if __name__ == '__main__':
fire.Fire(BinaryCanvas)
$ python example.py move 3 3 on move 3 6 on move 6 3 on move 6 6 on move 7 4 on move 7 5 on
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 1 0 0 1 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 1 0 0 1 0 0 0
0 0 0 0 1 1 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
コマンドクラスを表示用にカスタマイズする
- 呼んだメソッドの戻り値が何らかのクラスだった場合その
__str__
メソッドが呼ばれる
cliから関数呼び出しの色々
- 以下のようなファイルが有ったときにその呼出には複数の選択肢がある
- ハイフンとアンダースコアは置換可能
- 引数はポジション、名前付きどちらでもいい
- 名前付き引数とその値の=はあってもなくてもいい
import fire
class Building(object):
def __init__(self, name, stories=1):
self.name = name
self.stories = stories
def climb_stairs(self, stairs_per_story=10):
for story in range(self.stories):
for stair in range(1, stairs_per_story):
yield stair
yield 'Phew!'
yield 'Done!'
if __name__ == '__main__':
fire.Fire(Building)
$ python example.py --name="Sherrerd Hall" --stories=3 climb_stairs 10
$ python example.py --name="Sherrerd Hall" climb_stairs --stairs_per_story=10
$ python example.py --name="Sherrerd Hall" climb_stairs --stairs-per-story 10
$ python example.py climb-stairs --stairs-per-story 10 --name="Sherrerd Hall"
その他
コード自体にfireを埋め込まずにcliを作る
def hello(name):
return 'Hello {name}!'.format(name=name)
python -m fire example hello --name=World
# => Hello World!
cliでの引数の名前指定
- メソッドの引数はcliで名前指定しなくてもその順で引数として適用される
- しかし
—<arg-name>
で指定すると名前で適用できる
def say(name, content):
print(name + ", " + content)
fire.Fire(say)
python say.py taro hello # taro, hello
python say.py --content=hello --name=taro # taro, hello
cliで渡す引数が必須かオプションか
- 基本的にはすべての引数は必須になり、cliで引数を渡さないとエラー
- しかしメソッドで引数にデフォルト値を指定するとオプショナルになる
def say(name, content="hello"):
print(name + ", " + content)
fire.Fire(say)
python say.py taro # => taro, hello
python say.py # => error!
cliの引数でlistを渡す方法
python some.py "[test, test1]”
argにhelpメッセージをつける
- docstringに引数の説明を書くとそれがhelpになる
- 以下だと
python some.py -h
をするとa
にhogehogeと説明がでる
def main(a):
"""
a: hogehoge
"""