编程学习网 > 编程语言 > Python > 深入浅出 Typer: 用 Python 写出专业级命令行工具
2026
07-30

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


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

一、为什么是 Typerargparse 不香吗?

很多同学第一次写命令行工具,是用标准库自带的 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。换成 floatPathdatetime、枚举 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 commitgit 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 initpython 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 文件批量加前缀。综合运用 ArgumentOptionPath 和进度条:

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",直接用 boolTyper 自动支持 --flag / --no-flag

以上就是“深入浅出 Typer: 用 Python 写出专业级命令行工具的详细内容,想要了解更多Python教程欢迎持续关注编程学习网。 

扫码二维码 获取免费视频学习资料

Python编程学习

查 看2022高级编程视频教程免费获取