Pythonのコマンドラインツール作成方法
Pythonでは、標準ライブラリーのargparseを使って、コマンドラインツールを作ることができます。
また、サードパーティのライブラリーが、Awesome Python - Command-line Interface Development に紹介されています。
この中でGitHub上でStar数が最も多いのはPython Fireです。
Python Fireについて
Python Fireは、Google製のライブラリーです。pip install fire
でインストールすると使えます。
Python Fireを使うと、「コマンドラインからオブジェクトを自由に操作」できるようになります。
そのために必要なことは、オブジェクトをFireに登録することだけです。簡単に使い始められるというのがメリットです。
オブジェクトには、関数やクラスも含めて任意のオブジェクトが扱えます。
ドキュメントは、The Python Fire Guide です。
以下に主な使用方法を紹介します。
関数のコマンドライン化
Python Fireを使うには、from fire import Fire
して、Fire(コマンドラインで使用したい関数)
とします。つまり「使用したい関数をFire
に登録」だけです。
下記をsample.py
というファイル名で作成してください(以降も同名のファイルを扱います)。
from random import choice
from fire import Fire
Fire(choice)
python sample.py [1,2,3]
を実行すると、1, 2, 3
のいずれかが出力されます。これは、Pythonでprint(choice([1, 2, 3]))
を実行するのと同じになります。
また、python sample.py [大吉,吉,小吉]
を実行すると、大吉, 吉, 小吉
のいずれかが出力されます。
-
python sample.py ["大吉","吉","小吉"]
としなくても「大吉」などを文字列と認識します。 -
python sample.py [大吉, 吉, 小吉]
のようにスペースを入れるとエラーになるので注意してください。
Fire(choice)
とするだけで、簡単に、おみくじとして使うことができます。
ヘルプの表示
以下のいずれの方法でもヘルプを表示できます。
python sample.py --help
python sample.py -- --help
python sample.py -h
python sample.py -- -h
このように、Python Fireでは、自動的に「ヘルプを表示する機能」が使えます。
-
--help
あるいは-h
をつけることでヘルプが表示されます。- 「Choose a random element from a non-empty sequence.」という説明が出てきます。
-
--
を書くことによって、以降の引数が、sample.py
に対してはなくFire
に対するものになります。今回は、「--help
や-h
」がsample.py
の引数でないことが自明なので、--
は不要になっています。
また、自作の関数を登録して、詳しいヘルプを出したい場合は、その関数にdocstringを記述してください。
複数の関数のコマンドライン化
複数の関数を使えるようにするには、Fire
に辞書を渡します。
辞書のキーにコマンド名を、値に関数を指定します。
たとえば、Fire({"min": min, "max": max})
とすることで、min
とmax
の2つのコマンドが使えます。
python sample.py -h
またはpython sample.py
でヘルプを表示します。
ヘルプを見ると、python sample.py コマンド
のように使えて、コマンドとしてmin
とmax
が選べることがわかります。
-
python sample.py min 1 -2 3
を実行すると、min(1, -2, 3)
を計算して-2
と表示されます。 -
python sample.py max 1 -2 3
を実行すると、max(1, -2, 3)
を計算して3
と表示されます。
引数にリストを指定できます。
python sample.py min [1,-2,3]
を実行すると、min([1, -2, 3])
を計算して-2
と表示されます。
引数に文字列を指定できます。
python sample.py min cat
を実行すると、min("cat")
を計算してa
と表示されます。min("cat")
は、min(["c", "a", "t"])
と同じです。
辞書のキーがコマンドになります。
もし、Fire({"MIN": min, "MAX": max})
としていれば、python sample.py MIN 1 -2 3
のように使います。
辞書の要素数は、3つ以上でも可能です。
もし、Fire({"min": min, "max": max, "sum": sum})
としていれば、python sample.py sum [1,-2,3]
を実行すると、sum([1, -2, 3])
を計算して2と表示されます。
引数の文字列はPythonのコードのように評価され、適切な型のオブジェクトになって実行されます。
python sample.py min [1,-2,3]
を実行すると、min関数の引数はリストになります。
python sample.py min cat
を実行すると、min関数の引数は文字列になります。
Fire()のように空にした場合
Fire()
のように引数を指定しないと、そのモジュールに出てくる変数をすべて登録します。
from builtins import min, max
from fire import Fire as _Fire
_Fire()
上記のようにすると、Fire({"min": min, "max": max})
と同じになります。なお、_Fire
は、アンダースコアで始まっているため無視されます。
クラスのコマンドライン化
クラスを指定することもできます。
下記をsample.py
とします。
from fire import Fire
class Command:
min = min
max = max
Fire(Command)
Commandクラスは、min
とmax
のスタティックなメソッドを持っています。
このとき、Fire(Command)
とすることで、下記と同じように動作します。
c = Command()
Fire({"min": c.min, "max": c.max})
クラスを登録した場合でも、使い方は複数の関数の場合と変わりません。
実行例 | 結果 |
---|---|
python sample.py min cat |
a |
python sample.py max cat |
t |
python sample.py |
ヘルプを表示 |
python sample.py min -- --help |
minのヘルプを表示 |
python sample.py min cat
を実行すると、Command().min('cat')
を実行し結果を表示します。
メソッドを定義したクラスのコマンドライン化
スタティックでないメソッドを定義したクラスでは、以下のようになります。
from fire import Fire
class Command:
def min(self, *args):
"""return smallest args"""
return min(args)
def max(self, *args):
"""return largest args"""
return max(args)
Fire(Command)
Commandクラスのメソッドは、スタティックでないので、第1引数は自分自身(self
)にします。
実行例は以下のようになります。
実行例 | 結果 |
---|---|
python sample.py min 3 -2 1 |
-2 |
python sample.py max 3 -2 1 |
3 |
※ python sample.py min 3 -2 1
は、Command().min(3, -2, 1)
に対応し、min((3, -2, 1))
を計算し、-2
と出力されます。
※ python sample.py min cat
は、Command().min("cat")
に対応し、min(("cat", ))
を計算し、cat
と出力されることに注意してください。
__init__
メソッドを定義したクラスのコマンドライン化
前節では、python sample.py min
を実行すると、Command().min()
が呼ばれ、min(())
を実行しようとしてエラーになります。
一般に、min((), default=0)
のようにdefault
を指定すると、エラーにならずに0
を返すようになります。
ここでは、__init__
メソッドを作成し、min
のdefault
引数を指定できるようにします。
以下のように、Commandクラスの__init__
メソッドでdefault
を受け取れるようにし、それをmin
やmax
で使うようにします。
from fire import Fire
class Command:
def __init__(self, default=0):
self.default = default
def min(self, *args):
"""return smallest args"""
return min(args, default=self.default)
def max(self, *args):
"""return largest args"""
return max(args, default=self.default)
Fire(Command)
このようにすることで、引数が空でもエラーにならずに、0
を返します。
引数default
に値を指定するには、下記のように--default 値
というオプションをつけます。
実行例 | 結果 |
---|---|
python sample.py min |
0 |
python sample.py max --default -1 |
-1 |
別々の引数があるクラスのコマンドライン化
__init__
メソッドと、通常のメソッド(some
)の両方に引数がある場合は以下のようになります。
from fire import Fire
class Command:
def __init__(self, arg1=1):
self.arg1 = arg1
def some(self, arg2=2):
return self.arg1, arg2
Fire(Command)
実行例 | 結果 |
---|---|
python sample.py some |
[1, 2] |
python sample.py some --arg1 10 |
[10, 2] |
python sample.py some 20 |
[1, 20] |
python sample.py some --arg2 20 |
[1, 20] |
python sample.py some --arg1 10 20 |
[10, 20] |
python sample.py some --arg1 10 --arg2 20 |
[10, 20] |
-
メソッドの引数にデフォルト値があれば、コマンドラインで省略できます。
-
__init__
メソッドと通常のメソッドのどちらの引数も、同じように指定できます。同名の引数の場合、__init__
メソッドが優先されます。 -
--引数名
をつけない場合、通常のメソッドの引数と解釈されます。 -
Python Fireへのオプションを指定したい場合は、
python sample.py -- --help
のように--
の後に書きます。
補足
もし、def some(self, arg2):
のように定義していれば、下記はarg2
が指定されていないのでエラーになります。
python sample.py some
python sample.py some --arg1 10
サブコマンドについて
Python Fireでは、サブコマンドを持つツールも簡単に作成できます。
from fire import Fire
class Bin:
"""Convert binary number from/to decimal number."""
def from_dec(self, n):
"""Convert binary number from decimal number."""
return bin(n)
def to_dec(self, n):
"""Convert binary number to decimal number."""
return int(str(n), 2)
class Oct:
"""Convert octal number from/to decimal number."""
def from_dec(self, n):
"""Convert octal number from decimal number."""
return oct(n)
def to_dec(self, n):
"""Convert octal number to decimal number."""
return int(str(n), 8)
class Command:
bin = Bin
oct = Oct
Fire(Command)
- 上記のように記述することで、binコマンドとoctコマンドが使えます。
- binコマンドの値は、Binなので、Binクラスのメソッドをサブコマンドとして使えます。
- octコマンドの値は、Octなので、Octクラスのメソッドをサブコマンドとして使えます。
実行例 | 結果 | 意味 |
---|---|---|
python sample.py |
(略) | コマンドのヘルプ表示 |
python sample.py bin |
(略) | binコマンドのサブコマンドのヘルプ表示 |
python sample.py bin from_dec 3 |
0b11 |
10進数の数字3を2進数の数字に変換 |
python sample.py bin to_dec 11 |
3 |
2進数の数字11を10進数の数字に変換 |
python sample.py oct from_dec 9 |
0o11 |
10進数の数字9を8進数の数字に変換 |
python sample.py oct to_dec 11 |
9 |
8進数の数字11を10進数の数字に変換 |
補足その1
class Command:
bin = Bin
oct = Oct
Fire(Command)
上記の代わりに、Fire({"bin": Bin, "oct": Oct})
のように辞書で書いても同じ機能になります。
ただし、Commandクラスにはdocstringを書けますが、辞書では書けません。
補足その2
class Command:
bin = Bin()
oct = Oct()
Fire(Command)
上記のように、「クラスではなくオブジェクトを指定」しても同じように使えます。
コマンドチェーンについて
Python Fireでは、コマンドをつなげていくことができます。
python sample.py xxx
が返すオブジェクトがyyyという(メソッドなどの)属性を持っていれば、python sample.py xxx yyy
を実行できます。
また、それが返すオブジェクトがzzzという属性を持っていれば、python sample.py xxx yyy zzz
を実行できます。
これは、いくつでもつなげることができます。
下記をsample.py
とします。
import calendar
from fire import Fire
Fire(calendar)
python sample.py TextCalendar prmonth 2021 2
を実行すると下記のように、2021年2月のカレンダーが表示されます。
February 2021
Mo Tu We Th Fr Sa Su
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
-
Fire
では、calendar
モジュールを登録しています。 -
calendar
モジュールでは、TextCalendar
クラスが使えます。 -
TextCalendar
クラスでは、prmonth
というカレンダーを表示するメソッドが使えます。 -
prmonth
メソッドの引数は、年と月です。
以上から、python sample.py TextCalendar prmonth 2021 2
を実行すると、calendar.TextCalendar().prmonth(2021, 2)
に対応し、カレンダーが表示されます。
補足
Fireは、オプションで実行状態のトレースを出力できます。デバッグ時に役立ちます。
python sample.py TextCalendar prmonth 2021 2 -- --trace
とすると下記のように出力されます。
Fire trace:
1. Initial component
2. Accessed property "TextCalendar" (中略/calendar.py:293)
3. Instantiated class "TextCalendar" (中略/calendar.py:293)
4. Accessed property "prmonth" (中略/calendar.py:346)
5. Called routine "prmonth" (中略/calendar.py:346)
クラス指定、オブジェクト指定の違い
クラスを指定する例
class Command:
def __init__(self, hello='Hello'):
self.hello = hello
def say(self)
return self.hello
Fire(Command) # クラスを指定
python sample.py --hello Hi say
でHi
と出力されます。このように、実行時に__init__
メソッドの引数を指定できます。
オブジェクトを指定する例
class Command:
def __init__(self, hello='Hello'):
self.hello = hello
def say(self):
return self.hello
Fire(Command('Hi')) # オブジェクトを指定
python sample.py say
でHi
と出力されます。このように、実行時に指定していない__init__
メソッドの引数が使われます。__init__
メソッドの引数は変更できません。
クラス指定、オブジェクト指定の違いのまとめ
クラスを指定 | オブジェクトを指定 |
---|---|
ヘルプ中の文言「sample.py COMMAND 」 |
ヘルプ中の文言「sample.py GROUP 」 |
オブジェクトを自動で生成 | オブジェクトを明示的に生成 |
__init__ メソッドの引数を指定可能 |
__init__ メソッドの引数は指定不可 |
副作用のあるメソッドのコマンドチェーン
ここでは、print()
で結果を出力するメソッドをつなげる例を紹介します。
import random
from fire import Fire
class Command:
def __init__(self, *words):
self._words = list(words)
def s(self):
"""shuffle"""
random.shuffle(self._words)
print(" ".join(self._words))
return self
def r(self):
"""reverse"""
self._words.reverse()
print(" ".join(self._words))
return self
def __str__(self):
return ""
Fire(Command)
実行例
-
python sample.py A B C D E - s
:A〜Eをシャッフルして出力 -
python sample.py A B C D E - s s
:A〜Eをシャッフルして出力を繰り返す -
python sample.py A B C D E - s s r
:A〜Eをシャッフルして出力を繰り返し、反転し出力
コードの説明
-
-
引数は、現在のメソッド(__init__
メソッド)の引数の終わりを指示します。 -
s()
は、self._words
をシャッフルして出力します。 -
r()
は、self._words
を反転して出力します。 -
s()
やr()
はself
を返すので、最後に__str__
メソッドが呼ばれて空文字列を出力します。 -
_words
という代わりにwords
という名前のデータ属性にすると、words
というコマンドがヘルプに表示されます。 -
_words
のようにアンダースコアで始まる名前にすると、ヘルプには表示されませんが、利用はできます。
ブール型の引数
ブール型の引数は値を省略できます。
def show_args(args):
print(args)
Fire(show_args)
実行例
コマンド | 出力 |
---|---|
python sample.py --args |
True |
python sample.py --noargs |
False |
python sample.py --args True |
True |
python sample.py --args False |
False |
- 引数の値を省略すると、Trueを指定したとみなされます。
- 引数の名前に
no
をつけて、値を省略すると、Falseを指定したとみなされます。
以上