pexels-photo-266429.jpeg

爆速python-fire

 
0
このエントリーをはてなブックマークに追加
Kazuki Moriyama
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
    """
info-outline

お知らせ

K.DEVは株式会社KDOTにより運営されています。記事の内容や会社でのITに関わる一般的なご相談に専門の社員がお答えしております。ぜひお気軽にご連絡ください。