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 """
View Comments