写脚本容易,写"像模像样"的命令行工具难。Typer 来自 FastAPI 作者之手,让你用类型注解就生成带自动帮助、自动校验、子命令的专业 CLI。
很多同学第一次写命令行工具,是用标准库自带的 argparse。它能用,但写起来啰嗦:每个参数都要 add_argument 一长串;要做子命令得套一层 subparsers;帮助文档得自己一行行写。一个稍微像样的工具,配置文件比业务代码还长。
Typer 是当今 Python 写 CLI 最舒服的选择之一(GitHub 星标 15k+、月下载量千万级),它和 FastAPI 师出同门——作者都是 Sebastián Ramírez。它的核心哲学就一句话:类型注解即接口。你用 Python 的类型提示声明参数,Typer 自动把函数变成命令行入口、自动生成 --help、自动做类型转换与校验。
✅ 极简心智 — 函数 + 类型注解就是全部,不用记一堆 add_argument 参数。
📖 帮助自动生成 — 函数的 docstring、参数 help 自动变成专业帮助文档,连配色都帮你做好了。
🧱 底层稳健 — 它构建在超成熟的 click 之上,兼容 click 生态,稳得一批。
安装就一行:
pip install typer新建 hello.py,写一个普通函数,再用 typer.run 跑起来:
import typer def main(name: str): print(f"Hello {name}") if __name__ == "__main__": typer.run(main)终端里执行 python hello.py Alice,你会看到 Hello Alice。注意:name: str 这个类型注解,Typer 拿来当成命令行参数——你甚至没告诉它这是参数,它自己推断出来了。试试 python hello.py --help,一个完整帮助页已经躺在那了。
💡 心智模型:Typer 把"没默认值的参数"当成必填位置参数,"有默认值的参数"自动升级成 --选项。类型注解决定接受什么类型,str 收字符串、int 自动转整数、bool 自动识别开关。
这就是 Typer 最爽的地方。你声明类型,校验和转换全自动。比如要接收一个年龄(整数):
import typer def main(age: int): print(f"明年你就 {age + 1} 岁了") if __name__ == "__main__": typer.run(main)用户输入 python age.py 25 → 正常;输入 python age.py abc → Typer 直接报错:Invalid value for 'AGE': 'abc' is not a valid integer,连提示都帮你写好了,完全不用自己写 try/except。换成 float、Path、datetime、枚举 Enum,统统自动支持。
位置参数(Argument)用户必须按序传入;更多时候我们要的是"可选开关",用 typer.Option:
import typer def main( name: str = typer.Option(..., "--name", "-n", help="你的名字"), count: int = typer.Option(1, "--count", "-c", help="重复次数"), verbose: bool = typer.Option(False, "--verbose", "-v", help="是否啰嗦"), ): for _ in range(count): msg = f"Hello {name}" if verbose: msg += " (verbose mode)" print(msg) if __name__ == "__main__": typer.run(main)... 表示必填(用户不传就报错并提示);给了默认值的就是可选。--name / -n 同时支持长名和短名。布尔值更妙:传 --verbose 即为 True,不传就是 False,不用写 --verbose true 这么蠢的写法。
专业 CLI 往往是一组命令(想想 git commit、git push)。Typer 用 Typer() 应用 + @app.command() 装饰器轻松实现:
import typer app = typer.Typer() @app.command() def init(): """初始化项目""" print("已初始化项目") @app.command() def commit(message: str = typer.Option(..., "--message", "-m")): """提交改动""" print(f"已提交:{message}") if __name__ == "__main__": app()python tool.py init、python tool.py commit -m "fix bug" 各自运行。函数的 docstring 会自动变成该子命令的帮助说明——文档即代码,绝不断层。
有些参数用户更适合"被问一句再输",比如密码。Typer 一行搞定交互:
import typer def main( username: str = typer.Option(..., prompt="你的用户名"), password: str = typer.Option( ..., prompt=True, hide_input=True, help="登录密码(输入时不显示)", ), ): print(f"欢迎, {username}!(密码长度 {len(password)})") if __name__ == "__main__": typer.run(main)prompt=True 会让程序运行时主动问你;hide_input=True 让密码输入时变成小黑点;再加 confirmation_prompt=True 还能二次确认(改密码场景必备)。体验直接拉满。
还记得我们前面讲过的 Rich 吗?Typer 内置了对 Rich 的支持,彩色输出、表格、进度条信手拈来。最简单的彩色提示:
import typer def main(): typer.secho("成功!", fg=typer.colors.GREEN, bold=True) typer.secho("警告~", fg=typer.colors.YELLOW) typer.secho("出错了", fg=typer.colors.RED, bg=typer.colors.WHITE) if __name__ == "__main__": typer.run(main)想用更花的 Rich 组件?直接 from rich import print / Console 即可——因为 Typer 本来就把 Rich 当亲兄弟。报表、树形结构、Markdown 渲染,统统能塞进 CLI。
来写一个真正有用的小工具:给某个目录下所有 .txt 文件批量加前缀。综合运用 Argument、Option、Path 和进度条:
import typer from pathlib import Path app = typer.Typer() @app.command() def rename( folder: Path = typer.Argument(..., help="目标目录"), prefix: str = typer.Option("", "--prefix", "-p", help="加的前缀"), ): """批量给 .txt 文件加前缀""" files = list(folder.glob("*.txt")) if not files: typer.secho("没找到 .txt 文件", fg=typer.colors.YELLOW) raise typer.Exit(code=1) with typer.progressbar(files, label="重命名中") as bar: for f in bar: f.rename(f.with_name(f"{prefix}{f.name}")) typer.secho(f"完成!处理了 {len(files)} 个文件", fg=typer.colors.GREEN) if __name__ == "__main__": app()typer.progressbar 自带进度条;raise typer.Exit(code=1) 用标准退出码告诉调用方"出错了",其它程序或 shell 脚本能正确感知。运行 python tool.py ./docs -p "2026_" 即可。
想在所有子命令之前先执行一段逻辑(比如读配置、校验版本)?用 @app.callback():
import typer app = typer.Typer() @app.callback() def main(verbose: bool = typer.Option(False, "--verbose")): """我的超级工具集""" if verbose: print("详细模式已开启") @app.command() def run(): print("运行中...") if __name__ == "__main__": app()这样 --verbose 成了"全局开关",所有子命令都能用,而 callback 的 docstring 还成了整个工具的顶层说明。超适合做"瑞士军刀"型工具。
1. 忘记 if __name__ == "__main__": app():只定义了命令却没调用 app(),跑脚本啥也不发生。
2. 参数没写类型注解:Typer 靠注解推断参数类型,漏了 : str 这类标注,它不知道怎么解析,直接报错。
3. 把"可选参数"写成了 Argument:想做成 --xxx 就该用 Option,否则会被当成必填位置参数。
4. 子命令忘了 @app.command():函数定义好了却没装饰,Typer 根本不会注册它。
5. 布尔参数自己解析字符串:别写 flag: str 再判断 "true",直接用 bool,Typer 自动支持 --flag / --no-flag。
一句话总结:Typer 用最 Pythonic 的方式——类型注解——把"写命令行工具"从体力活变成创造乐趣。它和前面讲过的 FastAPI(同作者)、Pydantic、httpx、Rich、Loguru 同属现代 Python 工程"标配全家桶",强烈建议一起学。小工具、内部脚本、DevOps 辅助命令,Typer 几乎万能。

长按或扫描下方二维码,免费获取 Python公开课和大佬打包整理的几百G的学习资料,内容包含但不限于Python电子书、教程、项目接单、源码等等
▲扫描二维码-免费领取
推荐阅读
点击 阅读原文了解更多