argparseの定型コードをTyperが消す
PythonでCLIを書くとき、最初に手が伸びるのは標準ライブラリのargparseですよね。ただ引数を1つ増やすたびにadd_argumentを呼び、型変換とヘルプ文を別々に書く手間が残ります。同じCLIを、Typerは関数の型ヒントに寄せて短く書きます。
argparseは引数定義が三度手間
名前を受け取り、回数と敬語フラグを足しただけのスクリプト。argparseだとこうなります。
import argparse
def main():
parser = argparse.ArgumentParser(description="ユーザーに挨拶する")
parser.add_argument("name", help="挨拶する相手の名前")
parser.add_argument("--count", type=int, default=1, help="繰り返し回数")
parser.add_argument("--formal", action="store_true", help="敬語にする")
args = parser.parse_args()
greeting = "お世話になっております" if args.formal else "やあ"
for _ in range(args.count):
print(f"{greeting}, {args.name}")
if __name__ == "__main__":
main()
実行結果。
$ python argparse_ex.py 田中 --count 2 --formal
お世話になっております, 田中
お世話になっております, 田中
引数名・型・ヘルプ文がadd_argumentの中に文字列で散らばります。args.countの型はエディタからは追えません。
Typerは関数シグネチャがそのまま仕様
同じ挙動をTyperで書くと、引数定義は関数の引数そのものになります。
from typing import Annotated
import typer
def main(
name: str,
count: Annotated[int, typer.Option(help="繰り返し回数")] = 1,
formal: Annotated[bool, typer.Option(help="敬語にする")] = False,
):
"""ユーザーに挨拶する"""
greeting = "お世話になっております" if formal else "やあ"
for _ in range(count):
print(f"{greeting}, {name}")
if __name__ == "__main__":
typer.run(main)
$ python typer_ex.py 田中 --count 2 --formal
お世話になっております, 田中
お世話になっております, 田中
countはint、formalはbool。型ヒントを書いた時点で、型変換も--formal/--no-formalの生成もTyperが引き受けます。社内の集計スクリプトをargparseからTyperへ移したとき、引数定義は18行から9行に減りました。
インストールと最小構成
uvで入れて動かす
Typer 0.26.7はPython 3.10以上で動きます。uvでプロジェクトに追加します。
$ uv add typer
$ python -c "import typer; print(typer.__version__)"
0.26.7
外部のclickを別途入れる必要はありません。0.26.0でTyperがClick本体を内蔵したからです。パッケージ管理の使い分けはPythonのパッケージ管理はuvで統一すべき?pip・poetryとの違いに整理しています。
typer.run()で関数を1つだけCLIにする
コマンドが1つだけならtyper.run(関数)で十分。デコレータもクラスも要りません。引数なしで呼ぶと、Typerは自動生成した--helpを返します。
$ python typer_ex.py --help
Usage: typer_ex.py [OPTIONS] NAME
ユーザーに挨拶する
╭─ Arguments ──────────────────────────────────────────╮
│ * name TEXT [required] │
╰──────────────────────────────────────────────────────╯
╭─ Options ────────────────────────────────────────────╮
│ --count INTEGER 繰り返し回数 [default: 1] │
│ --formal --no-formal 敬語にする [default: no-formal] │
│ --help Show this message and exit. │
╰──────────────────────────────────────────────────────╯
docstringが説明文に、型ヒントが型表示に、デフォルト値が[default: 1]になりました。ヘルプ用の文字列を別に書いていません。
ArgumentとOptionはAnnotatedで書く
Typerの引数は2種類。位置引数のtyper.Argumentと、--name形式のtyper.Optionです。どちらもAnnotatedに包んで型ヒントへ添えます。
位置引数とオプションの違い
関数の引数にデフォルト値が無ければ位置引数、あればオプション。これがTyperの既定です。明示するなら次のように書きます。
from typing import Annotated
import typer
def main(
src: Annotated[str, typer.Argument(help="入力ファイル")],
dst: Annotated[str, typer.Option(help="出力先")] = "out.txt",
):
print(f"{src} -> {dst}")
if __name__ == "__main__":
typer.run(main)
$ python conv.py data.csv --dst result.txt
data.csv -> result.txt
$ python conv.py data.csv
data.csv -> out.txt
位置引数のsrcは必須。オプションのdstは省略するとout.txtに落ちます。
Annotated構文が公式の推奨
Typerは”the FastAPI of CLIs”を掲げ、タグラインは”build great CLIs. Easy to code. Based on Python type hints.”。型ヒントを軸に置く設計です。公式チュートリアルの”An alternative CLI argument declaration”では、Annotated版の使用を推奨しています。理由は”Annotated is part of standard Python”、標準の型注釈の仕組みにそのまま乗るからです。
旧構文との違い
古い書き方は、デフォルト値の位置にtyper.Option()を置きます。これだとPythonの「デフォルト値」とTyperの「オプション定義」が同じ場所に混ざります。
# 旧構文: デフォルト値の位置にOptionが入る
count: int = typer.Option(1, help="繰り返し回数")
# Annotated構文(推奨): 型とメタデータが分離する
count: Annotated[int, typer.Option(help="繰り返し回数")] = 1
どちらも動きます。ただAnnotated版ならcountの型はintのまま、デフォルトも= 1と通常のPython関数の形を保ちます。mypyやエディタの補完が素直に効きます。
サブコマンドでツールを束ねる
@app.command()で複数機能を持たせる
git addやgit commitのようにコマンドを分けたいとき、typer.Typer()のインスタンスを作り、各関数に@app.command()を付けます。
from typing import Annotated
import typer
app = typer.Typer(help="ファイル管理ツール")
@app.command()
def add(name: str):
"""項目を追加する"""
print(f"added: {name}")
@app.command()
def remove(
name: str,
force: Annotated[bool, typer.Option("--force", "-f")] = False,
):
"""項目を削除する"""
print(f"removed: {name} (force={force})")
if __name__ == "__main__":
app()
$ python tool.py --help
Usage: tool.py [OPTIONS] COMMAND [ARGS]...
ファイル管理ツール
╭─ Commands ───────────────────────────────────────────╮
│ add 項目を追加する │
│ remove 項目を削除する │
╰──────────────────────────────────────────────────────╯
関数名がそのままサブコマンド名に、docstringが各コマンドの説明になります。
短いフラグと真偽値オプション
typer.Option("--force", "-f")のように別名を渡すと、短いフラグも生やせます。
$ python tool.py remove old.log -f
removed: old.log (force=True)
Richの装飾とシェル補完で仕上げる
TyperはRichを依存に含み、ヘルプとエラーを罫線付きのパネルで表示します。Shellinghamも同梱で、補完対象のシェルを自動で判別します。
型エラーはRichのパネルで返る
--countに整数でない値を渡すと、変換に失敗してこう返ります。
$ python typer_ex.py 田中 --count abc
Usage: typer_ex.py [OPTIONS] NAME
Try 'typer_ex.py --help' for help.
╭─ Error ──────────────────────────────────────────────╮
│ Invalid value for '--count': 'abc' is not a valid integer. │
╰──────────────────────────────────────────────────────╯
バリデーションのコードは書いていません。型ヒントのintから、Typerが変換とエラー表示まで用意します。
補完は–install-completionで入る
サブコマンドを持つアプリには、--install-completionと--show-completionが自動で付きます。前者を実行すると、現在のシェル向けの補完がインストールされます。
$ python tool.py --install-completion
bash・zsh・fish・PowerShellに対応します。コマンド名やオプションをTab補完できます。
argparse・click・Typerの使い分け
| 観点 | argparse | click | Typer |
|---|---|---|---|
| 追加インストール | 不要(標準ライブラリ) | 必要 | 必要 |
| 定義スタイル | add_argument呼び出し | デコレータ | 型ヒント |
| 型変換 | type=で個別指定 | type=で個別指定 | 型ヒントから自動 |
| ヘルプ装飾 | 素のテキスト | 素のテキスト | Richで装飾 |
| エディタ補完 | 効きにくい | 効きにくい | 効く |
Typerは内部でClickを使うため、両者は対立しません。0.26.0からはClickを外部依存ではなく内蔵(vendoring)へ切り替えました。Typer側で依存バージョンを固定でき、Clickの更新と衝突しなくなった一方、Click向けのサードパーティプラグインやカスタム型はサポート対象外です。Clickのプラグインが必要なら、そこはClickを直接書きます。
まとめ
Typer 0.26.7で押さえた点。
- 引数定義は関数の型ヒントに寄せる。
add_argumentの文字列指定が消える - 引数・オプションは
Annotated構文で書く。公式が”An alternative CLI argument declaration”で推奨 - コマンドが1つなら
typer.run()、複数なら@app.command()でサブコマンド化 - 型変換・ヘルプ生成・エラー表示・シェル補完は型ヒントから自動で付く
- ClickはTyperに内蔵済み。別途インストールは不要、ただしClickプラグインは使えない
argparseのadd_argumentを並べる手間が気になるなら、Typerは型ヒントだけで同じCLIを組みます。

