
写脚本容易,写"像模像样"的命令行工具难。Typer 来自 FastAPI 作者之手,让你用类型注解就生成带自动帮助、自动校验、子命令的专业 CLI。
一、为什么是 Typer?argparse 不香吗?
很多同学第一次写命令行工具,是用标准库自带的 argparse。它能用,但写起来啰嗦:每个参数都要 add_argument 一长串;要做子命令得套一层 subparsers;帮助文档得自己一行行写。一个稍微像样的工具,配置文件比业务代码还长。
Typer 是当今 Python 写 CLI 最舒服的选择之一(GitHub 星标 15k+、月下载量千万级),它和 FastAPI 师出同门——作者都是 Sebastián Ramírez。它的核心哲学就一句话:类型注解即接口。你用 Python 的类型提示声明参数,Typer 自动把函数变成命令行入口、自动生成 --help、自动做类型转换与校验。
极简心智 — 函数 + 类型注解就是全部,不用记一堆 add_argument 参数。
帮助自动生成 — 函数的 docstring、参数 help 自动变成专业帮助文档,连配色都帮你做好了。
底层稳健 — 它构建在超成熟的 click 之上,兼容 click 生态,稳得一批。
二、安装与你的第一个 CLI
安装就一行:
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,统统自动支持。
四、选项 Options:可选参数与简写
位置参数(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 这么蠢的写法。
五、子命令:像 git 一样组织工具
专业 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 会自动变成该子命令的帮助说明——文档即代码,绝不断层。
六、交互式输入:prompt 与密码
有些参数用户更适合"被问一句再输",比如密码。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 天生一对
还记得我们前面讲过的 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 还成了整个工具的顶层说明。超适合做"瑞士军刀"型工具。
十、新手最常踩的 5 个坑
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: 用 Python 写出专业级命令行工具”的详细内容,想要了解更多Python教程欢迎持续关注编程学习网。
扫码二维码 获取免费视频学习资料

- 本文固定链接: http://www.phpxs.com/post/14367/
- 转载请注明:转载必须在正文中标注并保留原文链接
- 扫码: 扫上方二维码获取免费视频资料