🔖

Typerの使い方メモ

に公開

こんにちは。
kfskyです。普段はデータサイエンティストとして働いております。

最近、typerに触れる機会があったので、その使い方の備忘録として残しておきます。

Typerとは?

公式ドキュメントを翻訳したものです。

Typerは、ユーザーが使いやすく、開発者が作りやすいCLI(コマンドラインインターフェース)アプリケーションを構築するためのライブラリです。Pythonの型ヒントに基づいています。

また、スクリプトを実行するためのコマンドラインツールとしても機能し、それらを自動的にCLIアプリケーションに変換します。

typerの作者はFastAPIの作者と同じらしいです。

typerのインストール

pip install typer

Typerのメリット

基本的には、argpaserと同じ使い方しかしていないので、メリットの恩恵を最大限受けているわけでもないのですが...個人的には、以下がメリットかなと思っています。

  • シンプルに実装ができ、わかりやすい。
  • ターミナルでの表示がリッチになっている。
  • サブコマンドの設定が容易にできる。
  • コマンドライン引数での例外処理が容易

※:サブコマンドとは?
スクリプトのさらにその配下の関数などを選択して実行する方法です。( git addgit commit のような形でgitの中にさらにコマンドを選択して実行できるようになります。)

シンプルに実装ができ、わかりやすい

  • argparserでコマンドライン引数を実装する場合
import argparse

def main():
    parser = argparse.ArgumentParser(description="コマンドライン引数の処理")
    parser.add_argument("--name", required=True, type=str, help="ユーザーの名前")
    parser.add_argument("--age", required=True, type=int, help="ユーザーの年齢")
    parser.add_argument("--verbose", action="store_true", help="詳細出力の有無")

    args = parser.parse_args()

    if args.verbose:
        print(f"Verbose Mode: Name = {args.name}, Age = {args.age}")
    else:
        print(f"Hello {args.name}, you are {args.age} years old!")

if __name__ == "__main__":
    main()

コマンドラインに指定する引数が多くなるほど、parser.add_argumentの部分が増えていき、雑多な感じになっていきます。

  • typerでの実装の場合
import typer

# Typerのインスタンスを作成
app = typer.Typer()

@app.command()
def main(name: str, age: int, verbose: bool = False):
    """
    コマンドライン引数の処理
    - name: ユーザーの名前
    - age: ユーザーの年齢
    - verbose: 詳細出力の有無
    """
    if verbose:
        print(f"Verbose Mode: Name = {name}, Age = {age}")
    else:
        print(f"Hello {name}, you are {age} years old!")

if __name__ == "__main__":
    app()

main関数などに引数としてそのまま記載すれば、コマンドライン引数として設定ができるのが便利です。

シンプルに実装ができ、わかりやすい

typerだと、--helpでのコマンドラインの引数の詳細がわかりやすく整理できます。

$ python main.py goodbye --help

 Usage: main.py goodbye [OPTIONS] NAME

╭─ Arguments ───────────────────────────────────────╮
│ *    name      TEXT  [default: None] [required]   │
╰───────────────────────────────────────────────────╯
╭─ Options ─────────────────────────────────────────╮
│ --formal    --no-formal      [default: no-formal] │
│ --help                       Show this message    │
│                              and exit.            │
╰───────────────────────────────────────────────────╯

// Automatic --formal and --no-formal for the bool option 🎉

コマンドライン引数での例外処理が容易

他の方に自分が実装したスクリプトを渡す時に、コマンドライン引数を取れるようにしておくと、当然ながら想定していないような書き方をされる場合があります。( traineval しか取れないのに validate とコマンドライン引数に書いてしまうなど...)

その場合にエラーとして処理させるのではなく、例外処理を行い、実行を終了することがで来ます。

import typer

app = typer.Typer()

@app.command()
def main(name: str, age: int, verbose: bool = False):
    """
    コマンドライン引数の処理
    - name: ユーザーの名前 (文字列)
    - age: ユーザーの年齢 (整数)
    - verbose: 詳細出力の有無 (フラグ)
    """
    try:
        if not isinstance(name, str):
            raise ValueError("nameは文字列で指定してください。")
        if not isinstance(age, int):
            raise ValueError("ageは整数で指定してください。")
    except ValueError as e:
        typer.echo(f"エラー: {e}")
        raise typer.Exit(code=1)

    if verbose:
        print(f"Verbose Mode: Name = {name}, Age = {age}")
    else:
        print(f"Hello {name}, you are {age} years old!")

if __name__ == "__main__":
    app()

ここで、typer.Exit() はオプションで code パラメータを受け取ります。デフォルトでは code は 0 に設定されており、これはエラーが発生しなかったことを意味します。

エラーが発生したことをターミナル上で伝えたい場合は、0 以外の数値を code に渡すことができます。またプログラムを「中断」を通知させたいときは typer.Exit() を使用すると、画面に「Aborted!」と表示され、実行が中断されたことを明示することができます。

typerの使い方(引数設定の仕方)

必須の場合

import typer

app = typer.Typer()

@app.command()
def main(name: str = typer.Argument(default=...)):
    print(f"Hello {name}")

if __name__ == "__main__":
    app()

default=... を明示的に指定することで、引数を必須として明確にすることも可能です。

任意の場合

import typer
from typing import Optional

app = typer.Typer()

@app.command()
def main(
    name: str = typer.Argument(...),
    age: Optional[int] = typer.Option(None, help="Your age (optional)")
):
    if age:
        print(f"Hello {name}, you are {age} years old.")
    else:
        print(f"Hello {name}")

if __name__ == "__main__":
    app()

typer.Option のデフォルトを None にしておくことで、オプションとしての機能を実装することができます。また、 typer.Option(default=...) と設定すると、必須引数のように設定することができます。

まとめ

このレベルで覚えておけば普段の実装(コマンドライン引数)に対しては困らない気がしています。細かい設定など(例えば、helpコマンドをリッチにするとか、サブコマンドの使い方)などは公式のドキュメントがリッチなので、そちらを参照していただければと思います。

参考文献

Discussion