typer 是 FastAPI 作者做的命令行框架,核心卖点是「用类型注解定义参数」,写起来比 argparse 短很多。但它依赖 click 和 rich,启动成本不低。这篇把两边放在一起量了一遍。

同一个工具,两种写法

做一个有两个子命令的小工具:hello 打招呼(带次数和大声开关),add 两数相加。

typer 的写法是定义函数、标注类型、加装饰器,参数从类型注解里自动推导:

@app.command()
def hello(name: str, times: int = 1, loud: bool = False):
    """打招呼"""

name: str 自动变成必填位置参数,times: int = 1 变成带默认值的选项,loud: bool = False 自动变成 --loud 开关(布尔参数不需要值)。

argparse 要把这些关系一条条声明出来:

ph = sub.add_parser("hello")
ph.add_argument("name")
ph.add_argument("--times", type=int, default=1)
ph.add_argument("--loud", action="store_true")

还要自己写 if args.cmd == "hello" 的分发逻辑。

18 行对 25 行

同样两个功能,实测行数:

写法行数说明
typer18 行两个函数 + 两个装饰器 + 一行 app()
argparse25 行两个函数 + 三个 add_parser/add_argument + 分发

少 7 行,约 28%。子命令越多差距越大,因为 typer 一个装饰器就注册完了,argparse 每加一个参数都要一行。

代价:启动慢 565 毫秒

这是它容易被忽略的成本。同样跑 --help(不执行业务逻辑,只生成帮助),各测 5 次取中位:

脚本启动耗时(中位)最快 / 最慢
cli_typer.py1283 毫秒1262 / 1355
cli_argparse.py718 毫秒693 / 829

差了 565 毫秒,typer 是 argparse 的 1.8 倍。原因很直接:typer 要 import click,click 又拉起 rich 做那个带边框的帮助面板,这些 import 在每次启动时都要执行一遍。

那个帮助面板确实好看:分组、边框、对齐、中文都不会错位。但如果你的命令要被脚本反复调用,每次多 565 毫秒会拖慢整条流水线。

它还带什么

自动补全:--install-completion 一条命令把 shell 补全装好,bash / zsh / PowerShell 都支持。argparse 要自己写补全脚本。

类型校验自动做。传 --times abc 会直接报错并给出用法提示,不需要自己写 try/except 转换。

帮助文本从 docstring 来。函数下面的三引号内容直接变成命令说明,不用单独维护一份 help 字符串。

它还能生成命令行界面的交互式文档(基于 click 的子命令树),但那个功能对小工具意义不大。

什么时候用哪个

几行就能写完的小脚本,用 argparse 就够。标准库自带,不用装依赖,启动也快。

子命令多、参数多、需要好看帮助和自动补全的工具,typer 省下来的维护成本远超那 565 毫秒。

还有个折中方案:先用 argparse 写核心逻辑,等命令行接口复杂到一定程度再换 typer。因为 typer 的函数签名本身就是普通 Python 函数,迁移时业务代码不用动,只改外面那层壳。

如果对启动速度特别敏感(比如作为 git 钩子、编辑器插件被高频调用),可以考虑 click 裸用,去掉 rich 那层渲染,能省掉一部分开销,但帮助面板就没那么好看了。

补全那块实测有效:跑一次 --install-completion,它会把补全脚本写进当前 shell 的配置文件,之后敲 python cli_typer.py h 按 Tab 会补出 hello,参数名也能补。argparse 要实现同样的效果得自己接 argcomplete 的钩子。

帮助面板的中文对齐也值得说一句。argparse 的中文帮助在等宽字体里经常错位,因为它按字符数补空格,不认中文占两格。typer 走的是 rich 的宽度计算,实测 6 个中文字段全部对齐,不用手动调 padding。

没测的部分列一下,3 项:click 裸用的启动开销 / 自动补全在不同 shell 的实际体验 / 与 rust 系 CLI 框架的速度对比。