ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Typer 可选 CLI 参数(Optional Arguments)完全指南:从必填到默认值的两种声明方式

Typer 可选 CLI 参数(Optional Arguments)完全指南:从必填到默认值的两种声明方式 Typer 可选 CLI 参数Optional Arguments完全指南从必填到默认值的两种声明方式【免费下载链接】typerTyper, build great CLIs. Easy to code. Based on Python type hints.项目地址: https://gitcode.com/GitHub_Trending/ty/typer导读本文围绕 Typer 教程中可选 CLI 参数这一核心主题展开讲解如何把默认必填的CLI arguments改造为可选参数并系统对比Annotated元数据声明与旧式typer.Argument()默认值声明两种写法。读完本文你将掌握通过默认值控制参数可选性的底层机制、帮助输出中方括号/花括号的含义以及...Ellipsis显式声明必填的用法并能结合仓库源码理解其实现原理。默认约定选项可选、参数必填在 Typer 中存在两条默认约定这也是众多 CLI 程序的通用惯例CLI options选项默认是可选的CLI arguments位置参数默认是必填的。但默认约定不等于不可改变。事实上可选的CLI arguments在真实 CLI 程序中远比必填的CLI options常见得多。以 Linux/macOS 下的ls命令为例直接输入ls它列出当前目录下的文件和目录$ ls typer tests README.md LICENSE而给它一个可选的位置参数./tests/时它就列出该目录中的内容$ ls ./tests/ __init__.py test_tutorial这里的./tests/就是一个典型的可选 CLI 参数——它不叫--dir之类的选项而是直接作为位置值传给程序。这正是本文要解决的问题如何在 Typer 中声明这样的参数。另一种 CLI 参数声明方式Annotated在 First Steps 教程中你已经见过如何添加一个CLI argument——直接使用类型注解即可完整示例见 docs_src/first_steps/tutorial002_py310.pyimport typer def main(name: str): print(fHello {name}) if __name__ __main__: typer.run(main)Typer 还提供了一种等价的、但更强大的声明方式——借助 Python 标准库的Annotated。先看最小化写法使用typer.run()完整代码见 docs_src/arguments/optional/tutorial000_an_py310.pyfrom typing import Annotated import typer def main(name: Annotated[str, typer.Argument()]): print(fHello {name}) if __name__ __main__: typer.run(main)也可以显式创建Typer()实例并注册命令完整代码见 docs_src/arguments/optional/tutorial001_an_py310.pyfrom typing import Annotated import typer app typer.Typer() app.command() def main(name: Annotated[str, typer.Argument()]): print(fHello {name}) if __name__ __main__: app()注意Typer 从0.9.0版本起才支持并开始推荐使用Annotated。如果使用更早的版本会因无法解析Annotated元数据而报错。使用前请确认 Typer 版本不低于 0.9.0可通过pip show typer或uv pip show typer查看。Annotated与typer.Argument()的作用对比前后两种写法变化仅在于函数参数之前name: str现在name: Annotated[str]从 Python 语义上讲这两者完全等价——Annotated本就是标准库typing模块提供的类型 元数据包装机制并不会改变参数本身的类型。而Annotated的价值在于它允许我们在类型之外挂载额外的元数据Typer 会读取这些元数据来定制 CLI 行为name: Annotated[str, typer.Argument()]这里我们显式声明了name是一个CLI argument。它仍然是str类型而且因为没有默认值仍然是必填的。这一步与之前的name: str效果完全一致——一个必填参数$ uv run python main.py Usage: main.py [OPTIONS] {name} Try main.py --help for help. Error: Missing argument name.虽然这看起来没什么用但它是后续一切定制的基础只有先能显式声明typer.Argument()后面才能往里传参如default、help、metavar等来控制它的行为。制作一个可选的 CLI 参数要让CLI argument变为可选核心技巧是在使用typer.Argument()的同时为函数参数提供默认值例如World。完整代码见 docs_src/arguments/optional/tutorial002_an_py310.pyfrom typing import Annotated import typer app typer.Typer() app.command() def main(name: Annotated[str, typer.Argument()] World): print(fHello {name}!) if __name__ __main__: app()现在参数变成name: Annotated[str, typer.Argument()] World关键点在于只要函数参数带有默认值Typer 就能根据typer.Argument()判断它是CLI argument无论必填还是可选并根据默认值的存在与否决定它是否可选。观察--help输出先查看帮助信息$ uv run python main.py --help Usage: main.py [OPTIONS] [name] Arguments: name [default: World] Options: --help Show this message and exit.这里有两个值得注意的细节name仍然是CLI argument它依然显示在Usage: main.py ...中而非出现在Options:一栏现在[name]外面包裹的是方括号[和]表示该参数是可选的如果参数是必填的则使用花括号{和}回顾必填示例的帮助输出Usage: main.py [OPTIONS] {name}。实际运行验证不带参数运行默认值生效$ uv run python main.py Hello World!传入一个可选位置参数$ uv run python main.py Camila Hello Camila提示这里的Camila是作为CLI argument传入的而不是CLI option——我们没有使用--name Camila这种形式而是直接把Camila作为位置值传给程序。旧式写法typer.Argument()作为默认值除了Annotated元数据方式Typer 还支持另一种更早的旧式语法直接把typer.Argument()作为函数参数的默认值。完整代码见 docs_src/arguments/optional/tutorial001_py310.pyimport typer app typer.Typer() app.command() def main(name: str typer.Argument()): print(fHello {name}) if __name__ __main__: app()建议优先使用Annotated版本。它的好处是 Typer 的语义与 Python 本身的语义完全一致——Annotated[str, typer.Argument()] World中默认值仍然写在函数参数上Python 层面的是否必填/默认值是什么与 CLI 层面的表现一一对应无需记忆额外规则。为什么旧式写法需要default参数在旧式写法中Python 层面的语义发生了变化之前name: str没有默认值从 Python 角度看是必填参数Typer 据此把它生成为必填的CLI argument而现在name: str typer.Argument()有了默认值尽管是typer.Argument()这个特殊对象Python 层面已经不再必填。问题来了既然函数默认值的位置已经被typer.Argument()占用Typer 就失去了从是否有默认值来判断参数是否必填、默认值是什么的依据。因此typer.Argument()引入了自己的第一个参数default用它来表达这两件事。不传任何值给default等价于标记为必填name: str typer.Argument()也可以显式传入...Ellipsis来标记必填见 docs_src/arguments/optional/tutorial003_py310.pyimport typer app typer.Typer() app.command() def main(name: str typer.Argument(default...)): print(fHello {name}) if __name__ __main__: app()即name: str typer.Argument(default...)说明...是 Python 内置的特殊单值对象官方名称为Ellipsis省略号它在 Python 3 中是一个Ellipsis类型的单例常见于切片扩展语法等场景这里 Typer 借它来表达没有默认值、保持必填的语义。传入具体值则使其变为可选例如World见 docs_src/arguments/optional/tutorial002_py310.pyimport typer app typer.Typer() app.command() def main(name: str typer.Argument(defaultWorld)): print(fHello {name}!) if __name__ __main__: app()因为传入给typer.Argument(defaultWorld)的第一个参数是WorldTyper 据此判定这是一个可选的CLI argument命令行未提供值时使用默认值World。由于default是typer.Argument()的第一个位置参数社区代码中常见省略default的写法name: str typer.Argument(...) # 显式必填name: str typer.Argument(World) # 可选默认 World这些写法在功能上等价但正如上文所说——尽量使用Annotated这样代码在 Python 语义与 Typer 语义上保持一致无需记忆这些细节。源码视角typer.Argument()究竟能做什么从仓库源码可以进一步确认typer.Argument()的能力边界。其函数签名定义于 typer/params.py第一个位置参数正是default且默认值为...即不传时保持必填语义def Argument( # Parameter default: Any | None ..., *, callback: Callable[..., Any] | None None, metavar: str | None None, expose_value: bool True, is_eager: bool False, envvar: str | list[str] | None None, ... # TyperArgument show_default: bool | str True, show_choices: bool True, show_envvar: bool True, help: str | None None, hidden: bool False, ... ) - Any: ...从源码结构可以看出default是唯一的位置参数...作为默认哨兵值用于区分必填未提供 default与可选提供了具体 default两种状态其余参数全部为关键字参数覆盖了 CLI 参数定制的完整维度例如help帮助文本、metavar占位符显示名、envvar环境变量回退、show_default是否在帮助中显示默认值、min/max/clamp数值范围校验、exists/file_okay/dir_okay路径校验等在 typer/params.py 的文档字符串中Typer 也明确写道By default, CLI arguments are required. However, by giving them a default value they become optional并同时给出了旧式typer.Argument(World)标注为 deprecated与新式Annotated两种示例——这与本文的结论完全一致。因此默认值决定参数是否可选并非 Typer 的魔法而是Argument()对default哨兵值...与具体默认值进行区分后映射到 Click 底层参数声明的结果。测试用例佐证仓库的测试代码对本文所述行为做了完整的回归验证可进一步印证实现细节。针对可选参数默认值Worldtests/test_tutorial/test_arguments/test_optional/test_tutorial002.py 同时参数化测试了tutorial002_py310旧式与tutorial002_an_py310Annotated两个实现test_help断言帮助输出包含[OPTIONS] [name]验证可选参数的方括号用法test_call_no_arg不带参数调用断言输出Hello World!验证默认值生效test_call_arg传入Camila断言输出Hello Camila验证位置参数正常传递。针对显式必填default...tests/test_tutorial/test_arguments/test_optional/test_tutorial003.py 验证test_call_no_arg不带参数调用时退出码非 0且输出包含Missing argument name.证明...确实保持了必填语义test_call_arg传入Camila后正常输出Hello Camila。这些测试同时覆盖了Annotated与旧式两种声明方式说明 Typer 对两种写法一视同仁、行为完全一致。小结声明方式代码参数是否可选基础类型注解必填name: str必填Annotated 无默认值必填name: Annotated[str, typer.Argument()]必填Annotated 默认值可选name: Annotated[str, typer.Argument()] World可选默认World旧式 ...必填name: str typer.Argument(default...)必填旧式 具体默认值可选name: str typer.Argument(defaultWorld)可选默认World核心结论可以浓缩为一句话CLI 参数是否可选取决于是否提供了默认值——默认值存在即可选否则必填。推荐优先使用Annotated写法需 Typer ≥ 0.9.0因为它让 Python 层面的函数语义与 CLI 行为完全对齐旧式typer.Argument(default...)写法虽然仍被支持但已在源码文档中标注为 deprecated。掌握了这一点你就可以像ls那样为自己的 CLI 程序设计出缺省即合理的可选位置参数了。【免费下载链接】typerTyper, build great CLIs. Easy to code. Based on Python type hints.项目地址: https://gitcode.com/GitHub_Trending/ty/typer创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表