page contents

深入浅出 Typer:用 Python 写出专业级命令行工具

写脚本容易,写"像模像样"的命令行工具难。Typer 来自 FastAPI 作者之手,让你用类型注解就生成带自动帮助、自动校验、子命令的专业 CLI。

attachments-2026-08-I3Uh6Hvs6a6d50ba98729.png

写脚本容易,写"像模像样"的命令行工具难。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。

更多相关技术内容咨询欢迎前往并持续关注好学星城论坛了解详情。

想高效系统的学习Python编程语言,推荐大家关注一个微信公众号:Python编程学习圈。每天分享行业资讯、技术干货供大家阅读,关注即可免费领取整套Python入门到进阶的学习资料以及教程,感兴趣的小伙伴赶紧行动起来吧。

attachments-2022-05-rLS4AIF8628ee5f3b7e12.jpg

 

  • 发表于 2026-08-01 09:49
  • 阅读 ( 35 )
  • 分类:Python开发

你可能感兴趣的文章

相关问题

0 条评论

请先 登录 后评论
Pack
Pack

2307 篇文章

作家榜 »

  1. 轩辕小不懂 2403 文章
  2. Pack 2307 文章
  3. 小柒 2228 文章
  4. Nen 576 文章
  5. 王昭君 209 文章
  6. 文双 71 文章
  7. 小威 64 文章
  8. Cara 36 文章