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 行
同样两个功能,实测行数:
| 写法 | 行数 | 说明 |
|---|---|---|
| typer | 18 行 | 两个函数 + 两个装饰器 + 一行 app() |
| argparse | 25 行 | 两个函数 + 三个 add_parser/add_argument + 分发 |
少 7 行,约 28%。子命令越多差距越大,因为 typer 一个装饰器就注册完了,argparse 每加一个参数都要一行。
代价:启动慢 565 毫秒
这是它容易被忽略的成本。同样跑 --help(不执行业务逻辑,只生成帮助),各测 5 次取中位:
| 脚本 | 启动耗时(中位) | 最快 / 最慢 |
|---|---|---|
| cli_typer.py | 1283 毫秒 | 1262 / 1355 |
| cli_argparse.py | 718 毫秒 | 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 框架的速度对比。





