この記事ではPython Fireモジュールを紹介します。Python Fireはコマンドラインインターフェース(CLI)を自動生成するためのモジュールです。
Fireを用いるとargparse
などを手動で実装しなくても以下のようなコマンドが簡単に作成できます。
$ python3 sample1.py -- --help | cat
NAME
sample1.py
SYNOPSIS
sample1.py <flags>
FLAGS
--name=NAME
Default: 'World'
$ python3 sample1.py --name John
Hello John!
インストール
pip
コマンドでインストールすることができます。ライセンスはApache 2.0です。
$ pip install fire
基本的な使い方
特定の関数をコマンド化
関数をFire
関数に与えて呼び出すだけで、その関数がCLI化されます。
from fire import Fire
def hello(name="World"):
return "Hello %s!" % name
if __name__ == "__main__":
Fire(hello)
このコードを単純に実行すると、hello
関数が呼び出されます。コマンドライン引数を与えるとhello
関数へのname
引数として解釈されます。
$ python3 sample1.py
Hello World!
$ python3 sample1.py --name John
Hello John!
$ python3 sample1.py Bob
Hello Bob!
一方、--help
引数を与えて実行すると、自動生成されたCLIの使い方を説明してくれます。
$ python3 sample1.py -- --help | cat
NAME
sample1.py
SYNOPSIS
sample1.py <flags>
FLAGS
--name=NAME
Default: 'World'
なお、cat
のパイプライン処理は、ヘルプメッセージを標準出力に流すために追加しました。パイプを使わない場合、ヘルプメッセージは、環境変数PAGER
で指定されているページャーか、無ければPython Fire独自のページャーによって表示されます。テキストだけでは説明が難しいですが、別の言い方をすると| cat
がない場合、more
やman
コマンドのような別画面にヘルプが表示されます。
モジュール内の全ての関数をコマンド化
一方、Fire
関数を無引数で実行すると、モジュール内の全てのパブリックな関数がコマンド化されます。
from fire import Fire as _Fire
def hello(name="World"):
return "Hello %s!" % name
def bye(name="World"):
return "Bye %s!" % name
if __name__ == "__main__":
_Fire()
$ python3 sample2.py -- --help | cat
NAME
sample2.py
SYNOPSIS
sample2.py COMMAND
COMMANDS
COMMAND is one of the following:
hello
bye
hello
またはbye
サブコマンドを指定する必要がある以外はsample1.py
と同様の使い方です。
$ python3 sample2.py hello
Hello World!
$ python3 sample2.py hello --name John
Hello John!
$ python3 sample2.py hello Bob
Hello Bob!
存在しないサブコマンドを指定するとエラーになります。大文字小文字も区別されます。
$ python3 sample2.py Hello
ERROR: Cannot find key: Hello
Usage: sample2.py <command>
available commands: hello | bye
For detailed information on this command, run:
sample2.py --help
なお、Fire
関数を無引数で実行するとプライベート以外の全ての関数がコマンド化されます。sample2.pyでFire
関数を_Fire
にリネームしているのはFire
関数自体をコマンド化しないためです。
from fire import Fire
def hello(name="World"):
return "Hello %s!" % name
if __name__ == "__main__":
Fire()
$ python3 sample3.py -- --help | cat
NAME
sample3.py
SYNOPSIS
sample3.py COMMAND
COMMANDS
COMMAND is one of the following:
Fire
This function, Fire, is the main entrypoint for Python Fire.
hello
位置引数を持つ関数をコマンド化
関数の位置引数に対しては、CLIでもコマンドライン引数を明示的に与える必要があります。
from fire import Fire as _Fire
def hello(name):
return "Hello %s!" % name
if __name__ == "__main__":
_Fire(hello)
$ python3 sample4.py
ERROR: The function received no value for the required argument: name
Usage: sample4.py NAME
For detailed information on this command, run:
sample4.py --help
$ python3 sample4.py John
Hello John!
ただし、コード内の関数では位置引数であっても、コマンドライン引数ではオプション形式(FLAGS)の入力が可能です。
$ python3 sample4.py --name Bob
Hello Bob!
一歩進んだ使い方
ヘルプメッセージの追加
関数にdocstringを追加すれば、ヘルプメッセージにサブコマンドの説明が追加されます。また、関数の引数に型アノテーションを付与するとヘルプにも反映されます。
from fire import Fire
def hello(name: str):
"""A sample command for Fire
It only says hello."""
return "Hello %s!" % name
if __name__ == "__main__":
Fire(hello)
$ python3 sample5.py -- --help | cat
NAME
sample5.py - A sample command for Fire
SYNOPSIS
sample5.py NAME
DESCRIPTION
It only says hello.
POSITIONAL ARGUMENTS
NAME
Type: str
NOTES
You can also use flags syntax for POSITIONAL ARGUMENTS
関数の辞書をコマンド化
モジュールの全ての関数をコマンド化したいわけではないけれど、複数の関数をサブコマンド化したい、という場合はFire
に関数の辞書を与えます。
from fire import Fire
def hello(name="World"):
return "Hello %s!" % name
def bye(name="World"):
return "Bye %s!" % name
def boo(name="World"):
return "Boo %s!" % name
if __name__ == "__main__":
Fire({"say_hello": hello, "say_bye": bye})
$ python3 sample6.py -- --help | cat
NAME
sample6.py
SYNOPSIS
sample6.py COMMAND
COMMANDS
COMMAND is one of the following:
say_hello
say_bye
$ python3 sample6.py say_hello --name John
Hello John!
例のように、辞書に与えるキーを関数の識別子とは異なる文字列にすることで、コマンド名を変更することも可能です。
オブジェクトのコマンド化
Fire
関数にオブジェクトを与えると、メソッドをサブコマンド化することができます。
from fire import Fire
class Greeting:
def __init__(self, name):
self.name = name
def hello(self):
return "Hello %s!" % self.name
def bye(self):
return "Bye %s!" % self.name
if __name__ == "__main__":
Fire(Greeting("John"))
$ python3 sample7.py -- --help | cat
NAME
sample7.py
SYNOPSIS
sample7.py COMMAND | VALUE
COMMANDS
COMMAND is one of the following:
bye
hello
VALUES
VALUE is one of the following:
name
$ python3 sample7.py hello
Hello John!
クラスのコマンド化
オブジェクトではなくクラスをFire
関数に与えると__init__
をコマンド化することができます。__init__
によってオブジェクトが生成されたら、そのまま続けてメソッドや属性をサブコマンドとして繋げることができます。
from fire import Fire
class Calc:
"""Calculate + or * of two numbers."""
def __init__(self, num1, num2):
self.num1 = num1
self.num2 = num2
def add(self):
return self.num1 + self.num2
def mul(self):
return self.num1 * self.num2
if __name__ == "__main__":
Fire(Calc)
$ python3 sample8.py --num1=123 --num2=456 add
579
$ python3 sample8.py --num1=123 --num2=456 num1
123
使い方の詳細は--help
で確認することができます。
$ python3 sample8.py -- --help | cat
NAME
sample8.py - Calculate + or * of two numbers.
SYNOPSIS
sample8.py --num1=NUM1 --num2=NUM2
DESCRIPTION
Calculate + or * of two numbers.
ARGUMENTS
NUM1
NUM2
$ python3 sample8.py --num1=123 --num2=456 -- --help | cat
NAME
sample8.py --num1=123 --num2=456 - Calculate + or * of two numbers.
SYNOPSIS
sample8.py --num1=123 --num2=456 COMMAND | VALUE
DESCRIPTION
Calculate + or * of two numbers.
COMMANDS
COMMAND is one of the following:
add
mul
VALUES
VALUE is one of the following:
num1
num2
コマンドチェイン
Fireでは、コマンドの戻り値がオブジェクトの場合、そのオブジェクトのメソッドをコマンドチェインとして連続的に呼び出すことが可能です。__init__
でオブジェクトを生成した後にメソッドをチェインできるのも(おそらく)同じ仕組みです。以下の例では"hello John!"
という文字列にupper
メソッドやsplit
メソッドをチェインしました。
$ python3 sample1.py John
Hello John!
$ python3 sample1.py John upper
HELLO JOHN!
$ python3 sample1.py John split
Hello
John!
コマンドチェインの場合もヘルプを表示することができます。以下の例ではstring
オブジェクトのメソッドがヘルプ表示されています。
$ python3 sample1.py John -- --help | cat
NAME
sample1.py John - "Hello John!"
SYNOPSIS
sample1.py John COMMAND
DESCRIPTION
The string "Hello John!"
COMMANDS
COMMAND is one of the following:
capitalize
Return a capitalized version of the string.
casefold
Return a version of the string suitable for caseless comparisons.
...以下略
この機能を利用して、ステートフルなコマンドチェインを実現することも可能です。以下の例は、0で初期化された10×10の二次元配列に対し、move x y
で座標(x, y)に移動し、on
で1をセットする、という処理を繰り返すコマンドチェインです。
$ 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
この例では、ミュータブルなクラスの各メソッドでself
を返すことでメソッドの連続実行を実現しています。より詳しく知りたい方はPython Fire公式のガイドブックを参照してください。
引数のパースについて
コマンドライン引数はPythonコードとほとんど同じようにパースされます。
from fire import Fire
Fire(sum)
$ python3 sample9.py [1,2,3]
6
$ python3 sample9.py \(0.1,0.2,0.3\)
0.6000000000000001
バージョン情報
この記事のコードは以下のバージョンで検証しました。
- fire==0.4.0