LoginSignup
32
17

More than 1 year has passed since last update.

Python-Fireについて

Last updated at Posted at 2021-06-09

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})とすることで、minmaxの2つのコマンドが使えます。

python sample.py -hまたはpython sample.pyでヘルプを表示します。
ヘルプを見ると、python sample.py コマンドのように使えて、コマンドとしてminmaxが選べることがわかります。

  • 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クラスは、minmaxのスタティックなメソッドを持っています。
このとき、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__メソッドを作成し、mindefault引数を指定できるようにします。

以下のように、Commandクラスの__init__メソッドでdefaultを受け取れるようにし、それをminmaxで使うようにします。

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 sayHiと出力されます。このように、実行時に__init__メソッドの引数を指定できます。

オブジェクトを指定する例

class Command:
    def __init__(self, hello='Hello'):
        self.hello = hello
    def say(self):
        return self.hello

Fire(Command('Hi'))  # オブジェクトを指定

python sample.py sayHiと出力されます。このように、実行時に指定していない__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を指定したとみなされます。

以上

32
17
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
32
17