Python Fireの使い方

この記事では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がない場合、moremanコマンドのような別画面にヘルプが表示されます。

モジュール内の全ての関数をコマンド化

一方、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
よかったらシェアしてね!
  • URLをコピーしました!

この記事を書いた人

ITベンチャーでデータ分析、AI開発、システム設計、提案、営業、組織管理、公演、採用などなど多数の役割に従事してきました。

様々な職業や背景の方々と交流するうちに、幅広い分野で問題を解決したり価値を生み出したりするためには、個別の知識だけでなく、汎用的に物事を考える力を伸ばしていく必要があると考えるようになりました。

更に、自分自身の考える力だけでなく、より多くの人々の考える力のトレーニングを応援することで、社会全体を良くしていけるのではないかと考えて、このサイトを作りました。

目次
閉じる