最近(でもないですが)
色々なツールでcmd <subcommand>
と言った形でサブコマンドを引数に取るような
ものが増えてきました。
Pythonで素直に実装しようとするとArgumentParserとかを使って頑張る事になりますが、 Google製のPython Fireを使うと引数の取り回しを考えることなく そのようなツールを簡単に実装することができます。
Python Fire
Python Fireはコマンドラインツール(CLI)をクラスや関数から 簡単に作成するライブラリです。
使い方としては次の様にクラスなどを作ってそれを
実行時にfire.Fire
に喰わせるだけ。
クラスを使う
クラスを使う場合はこんな感じ。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
これでクラス内にあるadd
、sub
という関数名がそのまま
サブコマンドとして使える様になります。
$ chmod 755 calc_class
$ ./calc_class
Type: Calculator
String form: <__main__.Calculator object at 0x10b7084d0>
File: ~/calc_class
Docstring: A simple calculator class.
Usage: calc_class -
calc_class - add
calc_class - base
calc_class - sub
$ ./calc_class add
Fire trace:
1. Initial component
2. Instantiated class "Calculator" (./calc_class:6)
3. Accessed property "add" (./calc_class:11)
4. ('The function received no value for the required argument:', 'num')
Type: instancemethod
String form: <bound method Calculator.add of <__main__.Calculator object at 0x101fb74d0>>
File: ~/calc_class
Line: 11
Docstring: Addition function.
Usage: calc_class add NUM
calc_class add --num NUM
$ ./calc_class add 1
11
$ ./calc_class add --num 1
11
$ ./calc_class sub 1
9
$ ./calc_class add --base 5 1
6
$ ./calc_class sub --base 5 1
4
みたいな感じになります。
関数がサブコマンドになりますが、その引数はサブコマンドのあとに直接書く形で渡します。
--<引数名>
の様な形で渡すことも出来ます。
また、クラスをPython Fireに渡した場合、
--<コンストラクタの引数名>
とすることでコンストラクタの引数を渡すことも出来ます。
ちなみに最初のところでUsage
にbase
も入ってしまっていますが
Python Fireでは基本的に渡したObjectが持っているすべての通常Object(__init__
の様な__
で始まる特殊オブジェクト以外)
がサブコマンドとして登録されます。
変数の場合には単にその値が返されます。
$ ./calc base
10
関数を使う
同じ様なことを関数だけでも作ることが出来て
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
こんな感じのスクリプトを作って
$ ./calc_func
fire: <module 'fire' from '/usr/local/lib/python2.7/site-packages/fire/__init__.pyc'>
add: <function add at 0x1057e9668>
sub: <function sub at 0x105ccd7d0>
$ ./calc_func add
Fire trace:
1. Initial component
2. Accessed property "add"
3. ('The function received no value for the required argument:', 'num1')
Type: function
String form: <function add at 0x10d8f6668>
File: ~/calc_func
Line: 6
Docstring: Addition function.
Usage: calc_func add NUM1 NUM2
calc_func add --num1 NUM1 --num2 NUM2
$ ./calc_func add 1 2
3
$ ./calc_func sub 1 2
-1
こんな感じで使えます。
fire.Fire
を引数無しで呼ぶとそのファイルにあるオブジェクトを一通り組み込む様な感じになります。
関数をFireに直接与える
この場合に関数を与えると
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
$ ./calc_func2
Fire trace:
1. Initial component
2. ('The function received no value for the required argument:', 'num1')
Type: function
String form: <function add at 0x1026e2668>
File: ~/calc_func2
Line: 6
Docstring: Addition function.
Usage: calc_func 2NUM1 NUM2
calc_func 2--num1 NUM1 --num2 NUM2
$ ./calc_func2 1 2
3
の様にadd
だけが直接組み込まれます。
クラスのあるファイルでFireに引数を与えない場合
逆に最初のクラスの場合だと
$ ./calc_class2
fire: <module 'fire' from '/usr/local/lib/python2.7/site-packages/fire/__init__.pyc'>
Calculator: <class '__main__.Calculator'>
$ ./calc_class2 Calculator
Type: Calculator
String form: <__main__.Calculator object at 0x10277c4d0>
File: ~/calc_class2
Docstring: A simple calculator class.
Usage: calc_class2 Calculator -
calc_class2 Calculator - add
calc_class2 Calculator - base
calc_class2 Calculator - hoge
calc_class2 Calculator - sub
$ ./calc_class2 Calculator add 1
11
こんな感じでファイルにあるCalculator
が最初のサブコマンドとして認知され、
add
などの関数がそのサブサブコマンドとして使える様になります。
サブコマンドのグループ化
こういったオブジェクト内のオブジェクトがさらにまたサブコマンドになる、といった事を利用すると サブコマンド付きのサブコマンド、といったものが簡単に作れる様になります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|
$ ./calc_group
Type: Calculator
String form: <__main__.Calculator object at 0x10cf4a4d0>
File: ~/calc_group
Docstring: A calculator group example.
Usage: calc_group
calc_group add
calc_group sub
$ ./calc_group add
Type: Addition
String form: <__main__.Addition object at 0x10ff25510>
File: ~/calc_group
Usage: calc_group add
calc_group add calc
calc_group add name
$ ./calc_group add calc 1 2
3
$ ./calc_group sub calc 1 2
-1
$ ./calc_group sub name
Subtraction
$
さらにサブコマンドをネストしたければ上のAddition
でコンストラクタを作って
そこに新たなサブコマンド用のクラスをオブジェクトとして定義すれば良いだけです。
このあたりまで他のライブラリや自作で行おうとするとかなり面倒なことになるかと思いますが Python Fireだとこんな感じで簡単に出来ます。
helpについて
Python Fireでは上にも見た様にスクリプトだけ実行したり、 引数が必要なサブコマンドを引数無しで実行したりすると 自動である程度のヘルプが表示されます。
また、関数などの説明をdocstring
(先頭にあるコメント)で定義しておくことで
それを表示してくれます。
関数の説明も
$ ./calc_class add -- help
Fire trace:
1. Initial component
2. Instantiated class "Calculator" (./calc_class:6)
3. Accessed property "add" (./calc_class:12)
4. ('The function received no value for the required argument:', 'num')
Type: instancemethod
String form: <bound method Calculator.add of <__main__.Calculator object at 0x10ba714d0>>
File: ~/calc_class
Line: 12
Docstring: Addition function.
Usage: calc_class add NUM
calc_class add --num NUM
この様に--
で区切ったあとにhelp
を渡してあげることで関数のヘルプを呼び出すことが出来、
ここでも関数のdocstring
が表示されていることが分かります。
なのでdocstring
をきちんと書いておくと何もせずにそれなりのヘルプを作ることも可能です。
ただ、-- help
と呼ばないといけない、というのは知ってないと出来ないのでちょっと不親切です。
また、最初のクラスの例でbase
がサブコマンドとして表示されてしまうのも
ただ、最初のクラスの例だとコンストラクタで作るbase
がサブコマンドとして
表示されたりするのもちょっと嫌です。
なのでできればhelp
という関数を自分で
以下のようにすると良いかな、と思っています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
|
引数がないときは直接help
を呼ぶ様にしています。
まあそのあたりは好みですが。
似たようなライブラリ
似たようなコマンドラインツール用のライブラリでclickというものもあります。
こちらはまさにコマンドラインツールを作るのにArgparseが使いづらいからそれを なんとかした、という感じのもので デコレーターを使って引数を定義して行く感じになっています。
ヘルプなどの仕上がりも綺麗で、 サブサブコマンド的なネストも出来ます。
他にもいろいろと機能があるのでそのあたりで使いたい機能があるのであれば Clickを使ってみるのも良いかもしれません。
一方で Python Fireの良いところは作った関数やクラスが そのままサブコマンド付きコマンドラインツールにできてしまう、というお手軽さが 一番だと思うので、まずなにか作ってみたい、という場合にはPython Fireが一番オススメです。
まとめ
Python Fireを使うと引数の取り回しを自分でやらずに 簡単にサブコマンド付きのコマンドラインツールを作ることが出来ます。
実際、そのあたりを自作しようとするとけっこう大変なので。
ちなみにシェルスクリプトも引数の取り回しを考えるとサブコマンド付きツールというは結構面倒ですが、 Gitみたいに別コマンドを作ってそれをサブコマンドとして登録できる ライブラリみたいなものを以前作りました。
シェルスクリプトでサブコマンド付きツールを作ってみたい場合にはぜひ使ってみてください。