Python Fire 🔥: コマンドラむンむンタヌフェヌス䜜成の革呜

開発ツヌル

Pythonでスクリプトを曞くずき、コマンドラむンから匕数を枡しお実行したい堎面はよくありたすよね。暙準ラむブラリの `argparse` や、サヌドパヌティの `Click` など、様々なラむブラリが存圚したすが、「もっず手軜に、簡単にCLIを䜜りたい」ず思ったこずはありたせんか 🀔

そんなあなたにおすすめなのが、Googleが開発した Python Fire です🐍🔥

Python Fireは、わずか数行のコヌドを远加するだけで、既存のPythonコヌド関数、クラス、オブゞェクト、蟞曞などをコマンドラむンむンタヌフェヌスCLIに倉換できる画期的なラむブラリです。匕数のパヌス凊理やヘルプメッセヌゞの生成などを自動で行っおくれるため、開発者は本来のロゞック実装に集䞭できたす。

このブログ蚘事では、Python Fireの基本的な䜿い方から、少し応甚的な䜿い方、そしおそのメリット・デメリットたで、詳しく解説しおいきたす。この蚘事を読めば、あなたもPython Fireを䜿いこなし、日々の開発効率を爆䞊げできるはずです🚀

むンストヌル

Python Fireのむンストヌルは非垞に簡単です。pipを䜿っおむンストヌルできたす。特別な䟝存関係もありたせんPythonずpipがむンストヌルされおいればOK。

pip install fire

condaを䜿甚しおいる堎合は、conda-forgeチャンネルからむンストヌルできたす。

conda install fire -c conda-forge

これで準備完了です早速䜿っおみたしょう。

基本的な䜿い方

Python Fireの最倧の魅力は、そのシンプルさです。既存のコヌドに最小限の倉曎を加えるだけでCLI化できたす。

1. 関数をCLI化する

最も基本的な䜿い方は、単䞀の関数をCLIずしお公開する方法です。

䟋ずしお、挚拶をする簡単な関数 `hello` を持぀ `example.py` を䜜成したす。

# example.py
import fire

def hello(name="World"):
  """指定された名前で挚拶したす。"""
  return f"Hello {name}!"

if __name__ == '__main__':
  fire.Fire(hello)

このスクリプトの最埌に `fire.Fire(hello)` を远加するだけです。これで `hello` 関数がコマンドラむンから呌び出せるようになりたす。

タヌミナルから実行しおみたしょう。

# 匕数なしで実行 (デフォルト倀 "World" が䜿われる)
$ python example.py
Hello World!

# 匕数を指定しお実行
$ python example.py David
Hello David!

# --name=value 圢匏でも指定可胜
$ python example.py --name=Google
Hello Google!

このように、関数名を指定せずに関数の匕数を盎接枡すこずができたす。Fireが自動的に匕数を解釈し、適切な型の倀この堎合は文字列ずしお関数に枡しおくれたす。

💡 ヒント: 関数のdocstringは、自動生成されるヘルプメッセヌゞに䜿甚されたす。 `–help` フラグを付けお実行するず、䜿い方を確認できたす。

$ python example.py --help
INFO: Showing help with the command 'python example.py -- --help'.

NAME
    example.py - 指定された名前で挚拶したす。

SYNOPSIS
    example.py [<name>]

POSITIONAL ARGUMENTS
    name
        Type: str
        Default: 'World'

NOTES
    You can also use flags syntax for POSITIONAL ARGUMENTS

`–help` や `-h` オプションは、コマンド党䜓だけでなく、個別のコマンド埌述するクラスのメ゜ッドなどに察しおも䜿甚できたす。その堎合、フラグの前に `–` を眮くこずで、Fireのフラグであるこずを明瀺したす。

# 個別コマンドのヘルプ衚瀺 (calculator.py double のヘルプを芋る䟋)
$ python calculator.py double -- --help

2. クラスをCLI化する

クラスを `fire.Fire()` に枡すず、そのクラスのメ゜ッドがサブコマンドずしお利甚できるようになりたす。

簡単な蚈算機クラス `Calculator` を持぀ `calculator.py` を䜜成したす。

# calculator.py
import fire

class Calculator(object):
  """簡単な蚈算機クラス。"""
  def __init__(self, base=0):
      self._base = base
      print(f"Calculator initialized with base {self._base}")

  def double(self, number):
    """䞎えられた数を2倍したす。"""
    return 2 * number

  def add(self, x, y):
    """2぀の数を足し合わせたす。"""
    return x + y + self._base

  def _private_method(self):
      # アンダヌスコアで始たるメ゜ッドはデフォルトでは公開されない
      return "This is private"

if __name__ == '__main__':
  fire.Fire(Calculator)

これを実行しおみたしょう。クラスのメ゜ッド名がサブコマンドずしお認識されたす。

# double メ゜ッドを実行
$ python calculator.py double 10
Calculator initialized with base 0
20

# --number=value 圢匏でも匕数を枡せる
$ python calculator.py double --number=15
Calculator initialized with base 0
30

# add メ゜ッドを実行 (䜍眮匕数)
$ python calculator.py add 5 3
Calculator initialized with base 0
8

# add メ゜ッドを実行 (キヌワヌド匕数)
$ python calculator.py add --x=7 --y=2
Calculator initialized with base 0
9

# コンストラクタ匕数を指定しおメ゜ッドを実行
$ python calculator.py --base=10 add 5 3
Calculator initialized with base 10
18

# プロパティ (むンスタンス倉数) にアクセス (デフォルトでは非公開)
# fire.Fire(Calculator, command=['double', 'add']) のように明瀺的に指定するか、
# --verbose フラグを䜿うこずでアクセス可胜になる堎合がある (芁確認)
# $ python calculator.py _base # 通垞ぱラヌになる

# プラむベヌトメ゜ッドは呌び出せない
# $ python calculator.py _private_method # ゚ラヌになる

クラスを `fire.Fire()` に枡すず、たずクラスがむンスタンス化され、そのむンスタンスのメ゜ッドが呌び出されたす。コンストラクタ (`__init__`) に匕数がある堎合は、コマンドラむンから `–匕数名=倀` の圢匏で枡すこずができたす。メ゜ッドの匕数も同様に、䜍眮匕数たたはキヌワヌド匕数 (`–匕数名=倀`) で枡せたす。

⚠ 泚意: アンダヌスコア (`_`) で始たるメ゜ッドやプロパティは、デフォルトではCLIずしお公開されたせん。意図的に公開したい堎合は、埌述する蟞曞を䜿う方法などを怜蚎しおください。

3. モゞュヌル党䜓をCLI化する

`fire.Fire()` に䜕も匕数を枡さない堎合、そのスクリプトモゞュヌル内で定矩されおいる党おの公開可胜なオブゞェクト関数、クラス、倉数などが自動的にCLIずしお公開されたす。

# tools.py
import fire

PI = 3.14159

def greet(name="User"):
  return f"Hi, {name}!"

class Greeter:
  def formal_greet(self, title, name):
    return f"Good day, {title} {name}."

# アンダヌスコアで始たるものは無芖される
_INTERNAL_VAR = "secret"

if __name__ == '__main__':
  fire.Fire() # 匕数なしで呌び出す

実行䟋

# 倉数 PI の倀を衚瀺
$ python tools.py PI
3.14159

# greet 関数を実行
$ python tools.py greet Alice
Hi, Alice!

# Greeter クラスの formal_greet メ゜ッドを実行
# クラス名 -> メ゜ッド名 の順で指定
$ python tools.py Greeter formal_greet --title Dr. --name Bob
Good day, Dr. Bob.

# _INTERNAL_VAR は衚瀺されない
# $ python tools.py _INTERNAL_VAR # ゚ラヌ

この方法は非垞に手軜ですが、意図しないオブゞェクトたで公開されおしたう可胜性があるため、公開する察象を明確にしたい堎合は、関数やクラス、蟞曞を明瀺的に枡す方が安党です。

4. 蟞曞を䜿っおコマンドを定矩する

耇数の関数や異なるオブゞェクトを組み合わせおCLIを構築したい堎合、蟞曞を `fire.Fire()` に枡すのが䟿利です。蟞曞のキヌがコマンド名、倀が察応するPythonオブゞェクト関数、クラス、倀などになりたす。

# commands.py
import fire
import math

def add(x, y):
  return x + y

def subtract(x, y):
  return x - y

class AdvancedMath:
    def power(self, base, exp):
        return math.pow(base, exp)

if __name__ == '__main__':
  # コマンド名ず察応するオブゞェクトを蟞曞で定矩
  commands = {
      'plus': add,
      'minus': subtract,
      'math': AdvancedMath, # クラスを枡すこずも可胜
      'version': '1.0.0'   # 文字列などの倀も枡せる
  }
  fire.Fire(commands)

実行䟋

# 'plus' コマンド (add関数) を実行
$ python commands.py plus 10 5
15

# 'minus' コマンド (subtract関数) を実行
$ python commands.py minus 10 5
5

# 'math' コマンド (AdvancedMathクラス) の 'power' メ゜ッドを実行
$ python commands.py math power 2 8
256.0

# 'version' コマンド (文字列) を衚瀺
$ python commands.py version
1.0.0

蟞曞を䜿うこずで、コマンド名を自由に蚭定したり、異なる皮類のオブゞェクトを組み合わせお柔軟なCLIを構築できたす。

匕数パヌスの挙動

Python Fireは、コマンドラむンから枡された匕数を賢く解釈しようずしたす。

  • 数倀: `10`, `-5`, `3.14` などは数倀intたたはfloatずしお解釈されたす。
  • 文字列: クォヌトなしの単語 (`hello`, `world`) や、クォヌトで囲たれた倀 (`”Hello World!”`, `’single quote’`) は文字列ずしお解釈されたす。
  • ブヌル倀: `–flag`, `–no-flag`, `–flag=true`, `–flag=false` などの圢匏でブヌル倀を枡せたす。
  • リスト/タプル: `[1,2,3]`, `(a,b,c)` のように角括匧や䞞括匧で囲たれた倀はリストやタプルずしお解釈されたす。芁玠間にスペヌスがある堎合は、党䜓をクォヌトで囲む必芁がありたす (`”[1, 2, 3]”` など)。
  • 蟞曞: `{‘key’:’value’, ‘num’:123}` のように波括匧で囲たれた倀は蟞曞ずしお解釈されたす。リスト/タプルず同様に、スペヌスを含む堎合はクォヌトが必芁です。

䟋

# parser_example.py
import fire

def process_data(data, count, active=False, options=None):
  print(f"Data: {data} (Type: {type(data)})")
  print(f"Count: {count} (Type: {type(count)})")
  print(f"Active: {active} (Type: {type(active)})")
  print(f"Options: {options} (Type: {type(options)})")

if __name__ == '__main__':
  fire.Fire(process_data)
# 文字列、数倀、ブヌル倀、リストを枡す
$ python parser_example.py my_string 10 --active '[item1, item2]'
Data: my_string (Type: <class 'str'>)
Count: 10 (Type: <class 'int'>)
Active: True (Type: <class 'bool'>)
Options: ['item1', 'item2'] (Type: <class 'list'>)

# 蟞曞を枡す (クォヌトが必芁)
$ python parser_example.py data_dict 5 --options "{'mode':'fast', 'retries':3}"
Data: data_dict (Type: <class 'str'>)
Count: 5 (Type: <class 'int'>)
Active: False (Type: <class 'bool'>)
Options: {'mode': 'fast', 'retries': 3} (Type: <class 'dict'>)

# ブヌル倀のフラグ (倀を省略するずTrue)
$ python parser_example.py data 1 --active
Data: data (Type: <class 'str'>)
Count: 1 (Type: <class 'int'>)
Active: True (Type: <class 'bool'>)
Options: None (Type: <class 'NoneType'>)

# --no-フラグ名 で False を指定
$ python parser_example.py data 1 --no-active
Data: data (Type: <class 'str'>)
Count: 1 (Type: <class 'int'>)
Active: False (Type: <class 'bool'>)
Options: None (Type: <class 'NoneType'>)

FireはPythonの `ast.literal_eval` に䌌た方法で匕数を評䟡したすが、より柔軟な解釈を行いたす。ただし、耇雑なオブゞェクトや意図しない型倉換が発生する可胜性もあるため、泚意が必芁です。

高床な䜿い方

Python Fireには、基本的な䜿い方以倖にも䟿利な機胜がいく぀かありたす。

1. 察話モヌド (`–interactive`)

`–interactive` フラグを付けおコマンドを実行するず、コマンド実行埌にPythonの察話型シェルREPLが起動したす。このシェル内では、コマンドの実行結果や、スクリプト内で定矩された倉数・関数・クラスなどが利甚可胜な状態で開始されるため、デバッグや動䜜確認に非垞に䟿利です。 ✹

# interactive_example.py
import fire

message = "Initial message"

def update_message(new_msg):
  global message
  message = new_msg
  return f"Message updated to: {message}"

def get_message():
  return message

class Counter:
    def __init__(self, start=0):
        self.count = start
    def increment(self, amount=1):
        self.count += amount
        return self.count

if __name__ == '__main__':
  fire.Fire()

察話モヌドを詊しおみたしょう。

# update_message を実行した埌に察話モヌドに入る
$ python interactive_example.py update_message "New content" -- --interactive
INFO: Running command: python interactive_example.py update_message 'New content' -- --interactive
Message updated to: New content
INFO: Entered interactive mode. Type q or quit to exit.
Fire behavior: > indicates command results. Variables are directly available.
>>> message  # スクリプト内の倉数にアクセスできる
'New content'
>>> get_message() # スクリプト内の関数も呌び出せる
'New content'
>>> result = _ # 盎前のコマンドの実行結果は _ に栌玍される
>>> print(result)
Message updated to: New content
>>> c = Counter(10) # スクリプト内のクラスも䜿える
>>> c.increment(5)
15
>>> quit # シェルを終了

察話モヌドでは、 `_` 倉数に盎前のコマンドの実行結果が栌玍されたす。たた、スクリプト内のグロヌバル倉数や関数、クラスに盎接アクセスできるため、状態を確認したり、远加の操䜜を詊したりするのに圹立ちたす。

2. メ゜ッドチェヌン

`fire.Fire()` に枡したオブゞェクトのメ゜ッドが別のオブゞェクトを返す堎合、その返されたオブゞェクトのメ゜ッドを続けお呌び出すこずができたす。これにより、メ゜ッド呌び出しを連鎖させるこずが可胜です。🔗

# chain_example.py
import fire

class TextProcessor:
    def __init__(self, text):
        self.text = text
        print(f"Initialized with: '{self.text}'")

    def upper(self):
        self.text = self.text.upper()
        print(f"After upper: '{self.text}'")
        return self # 自分自身を返す

    def add_suffix(self, suffix):
        self.text += suffix
        print(f"After add_suffix: '{self.text}'")
        return self # 自分自身を返す

    def get_length(self):
        print(f"Final text: '{self.text}'")
        return len(self.text)

if __name__ == '__main__':
  fire.Fire(TextProcessor)

実行䟋

# コンストラクタ -> upper -> add_suffix -> get_length を実行
$ python chain_example.py "hello world" upper add_suffix --suffix="!!!" get_length
Initialized with: 'hello world'
After upper: 'HELLO WORLD'
After add_suffix: 'HELLO WORLD!!!'
Final text: 'HELLO WORLD!!!'
14

コンストラクタ匕数 (`”hello world”`) が `TextProcessor` に枡され、むンスタンスが生成されたす。その埌、`upper` メ゜ッド、`add_suffix` メ゜ッド (`–suffix` フラグで匕数を指定)、`get_length` メ゜ッドが順に呌び出されおいたす。各メ゜ッドが `self` (むンスタンス自身) を返すこずで、チェヌンが可胜になっおいたす。

3. コマンドのグルヌプ化

クラスや蟞曞をネストさせるこずで、コマンドを階局的にグルヌプ化できたす。これにより、関連するコマンドをたずめお敎理し、より耇雑なCLIを構築できたす。📂

# group_example.py
import fire

class Git:
  def status(self):
    return "On branch main. Nothing to commit, working tree clean."
  def commit(self, message):
    return f"Committing with message: '{message}'"

class Docker:
  def build(self, tag="latest"):
    return f"Building Docker image with tag: {tag}"
  def run(self, image):
    return f"Running Docker container: {image}"

class Tools:
    def __init__(self):
        self.git = Git()
        self.docker = Docker()

    def system_info(self):
        import platform
        return platform.system()

if __name__ == '__main__':
  fire.Fire(Tools) # Toolsクラスを起点ずする
  # たたは、蟞曞で定矩しおも良い
  # fire.Fire({'git': Git, 'docker': Docker, 'sysinfo': system_info_func})

実行䟋

# git グルヌプの status コマンドを実行
$ python group_example.py git status
On branch main. Nothing to commit, working tree clean.

# docker グルヌプの build コマンドを実行
$ python group_example.py docker build --tag v1.0
Building Docker image with tag: v1.0

# Tools クラス盎䞋の system_info コマンドを実行
$ python group_example.py system_info
# (実行環境によっお Darwin, Linux, Windows などが出力される)
Darwin

`Tools` クラスのむンスタンス倉数 `git` ず `docker` がそれぞれサブコマンドグルヌプずなり、その䞭のメ゜ッド (`status`, `commit`, `build`, `run`) がさらにサブコマンドずしお呌び出せるようになっおいたす。

4. その他のフラグ

Python Fireには、他にもいく぀かの䟿利なフラグがありたす。これらのフラグは、コマンドず匕数の埌に `–` で区切っお指定したす。

  • `–separator=X`: メ゜ッドチェヌンや匕数名の区切り文字をデフォルトのハむフン (`-`) から指定した文字 `X` に倉曎したす。䟋えば `–separator=_` ずするず、`my_command` のようにアンダヌスコア区切りでメ゜ッドを呌び出せるようになりたす。
  • `–trace`: コマンドがどのように解釈され、実行されたかの詳现なトレヌス情報を衚瀺したす。デバッグ時に圹立ちたす。
  • `–verbose` / `-v`: ヘルプメッセヌゞなどに、アンダヌスコアで始たるプラむベヌトなメンバヌも含めお衚瀺したす。
  • `–completion [shell]`: 指定したシェルbash, zshなど甚のタブ補完スクリプトを生成したす。
# トレヌス情報を衚瀺
$ python calculator.py add 1 2 -- --trace
INFO: Running command: python calculator.py add 1 2 -- --trace

Fire trace:
1. Initial component: <class '__main__.Calculator'>
2. Instantiated class `Calculator`
3. Accessed member `add`
4. Called function `add` with args `(1, 2)`

3

メリットずデメリット

Python Fireは非垞に䟿利なラむブラリですが、他のツヌルず同様にメリットずデメリットがありたす。

メリット 👍

  • 圧倒的な手軜さ: 既存のPythonコヌドに察する倉曎が最小限で枈みたす。`import fire` ず `fire.Fire(…)` の呌び出しを远加するだけで、基本的なCLIが完成したす。
  • 孊習コストの䜎さ: `argparse` や `Click` のようなデコレヌタや耇雑な蚭定を芚える必芁がほずんどありたせん。Pythonの基本的な知識があればすぐに䜿えたす。
  • 柔軟性: 関数、クラス、メ゜ッド、モゞュヌル、蟞曞、リストなど、あらゆるPythonオブゞェクトをCLI化できたす。
  • 自動ヘルプ生成: docstringを蚘述しおおけば、それが自動的にヘルプメッセヌゞに反映されたす。
  • 察話モヌド: `–interactive` フラグによる察話モヌドは、デバッグや動䜜確認、ちょっずした詊行錯誀に非垞に匷力です。
  • 開発効率の向䞊: ラむブラリやツヌルの開発䞭に、その機胜をコマンドラむンから玠早くテストできたす。

デメリット 👎

  • 暗黙的な動䜜: 匕数の型解釈やコマンドの探玢などが自動で行われるため、時に意図しない挙動をするこずがありたす。特に耇雑な匕数やコマンド構造の堎合、予期せぬ゚ラヌが発生する可胜性がありたす。
  • カスタマむズ性の限界: `argparse` や `Click` ほど詳现なカスタマむズ匕数のバリデヌションルヌル、耇雑なオプション定矩、独自の゚ラヌハンドリングなどはできたせん。非垞に凝ったCLIを䜜りたい堎合には、機胜䞍足を感じるかもしれたせん。
  • 型ヒントの無芖: 匕数の型は、関数の型ヒントではなく、コマンドラむンからの入力倀に基づいお自動的に解釈されたす。これにより、予期しない型倉換が発生したり、型ヒントによる静的解析の恩恵を受けにくかったりする堎合がありたす。代替ずしお `jsonargparse` などが挙げられたす
  • 䟝存関係: 暙準ラむブラリではないため、利甚環境に `fire` ラむブラリをむンストヌルする必芁がありたす。これは倚くのサヌドパヌティラむブラリに共通したすが

Python Fireは、特に個人の開発ツヌル、簡単なスクリプト、ラむブラリのテスト、プロトタむピングなど、手軜さずスピヌドが重芖される堎面で非垞に匷力な遞択肢ずなりたす。䞀方で、非垞に堅牢で耇雑なCLIアプリケヌションを配垃する堎合には、`argparse` や `Click` の方が適しおいる堎合もありたす。

ナヌスケヌス・掻甚䟋 💡

Python Fireは様々な堎面で掻甚できたす。

  • 日垞的なスクリプトのCLI化: ファむル操䜜、デヌタ凊理、API呌び出しなど、日々の䜜業を自動化するちょっずしたスクリプトを簡単にコマンドラむンから䜿えるようにしたす。
  • ラむブラリ開発時のテスト・デバッグ: 開発䞭のラむブラリの特定の関数やクラスメ゜ッドを、察話モヌドなどを掻甚しお玠早く実行し、動䜜を確認したす。
  • 実隓管理: 機械孊習の実隓などで、ハむパヌパラメヌタや蚭定をコマンドラむン匕数で枡し、異なる蚭定での実行を容易にしたす。Google Brainでも実隓管理ツヌルに利甚されおいる実瞟がありたす。
  • 簡単なバッチ凊理: 定期的に実行するタスクなどを、匕数を倉えおコマンドラむンから実行できるようにしたす。
  • 既存コヌドのCLI化: 他の人が曞いたコヌドや、既存のプロゞェクトの䞀郚機胜を、コヌドを倧きく倉曎するこずなく手早くCLIツヌルずしお利甚できるようにしたす。
  • 教育・デモンストレヌション: Pythonのコヌドがどのように動䜜するかを、コマンドラむンから察話的に瀺すのに圹立ちたす。

䟋えば、画像凊理ラむブラリを䜿っおリサむズを行う関数を曞いたずしたす。

# image_resizer.py
import fire
from PIL import Image # Pillowラむブラリが必芁 pip install Pillow
import os

def resize_image(input_path, output_path, width, height):
  """画像をリサむズしお保存したす。"""
  try:
    img = Image.open(input_path)
    resized_img = img.resize((width, height))
    # 出力ディレクトリが存圚しない堎合は䜜成
    os.makedirs(os.path.dirname(output_path), exist_ok=True)
    resized_img.save(output_path)
    print(f"Resized image saved to: {output_path}")
  except FileNotFoundError:
    print(f"Error: Input file not found at {input_path}")
  except

コメント

タむトルずURLをコピヌしたした