可选 CLI 参数
我们之前说过默认情况下
- CLI 选项是可选的
- CLI 参数是必需的
同样,这就是它们默认情况下的工作方式,这也是许多 CLI 程序和系统中的惯例。
但你可以更改它。
事实上,拥有可选的CLI 参数非常常见,比拥有必需的CLI 选项常见得多。
作为一个示例来说明它如何有用,让我们看看 ls
CLI 程序如何工作。
// If you just type
$ ls
// ls will "list" the files and directories in the current directory
typer tests README.md LICENSE
// But it also receives an optional CLI argument
$ ls ./tests/
// And then ls will list the files and directories inside of that directory from the CLI argument
__init__.py test_tutorial
一种替代的CLI 参数声明¶
在入门指南中,您了解了如何添加CLI 参数
import typer
def main(name: str):
print(f"Hello {name}")
if __name__ == "__main__":
typer.run(main)
现在,我们来看看创建相同CLI 参数的另一种方法
import typer
from typing_extensions import Annotated
def main(name: Annotated[str, typer.Argument()]):
print(f"Hello {name}")
if __name__ == "__main__":
typer.run(main)
信息
Typer 在 0.9.0 版本中增加了对Annotated
的支持(并开始推荐使用它)。
如果您使用的是较旧版本,则在尝试使用Annotated
时会收到错误。
在使用Annotated
之前,请确保将 Typer 版本升级到至少 0.9.0。
之前,您有这个函数参数
name: str
现在,我们用Annotated
包装它
name: Annotated[str]
这两个版本的意思相同,Annotated
是标准 Python 的一部分,并且在这里用于此目的。
但是,使用Annotated
的第二个版本允许我们传递Typer可以使用的其他元数据
name: Annotated[str, typer.Argument()]
现在,我们明确指出name
是一个CLI 参数。它仍然是一个str
,并且仍然是必需的(它没有默认值)。
我们在那里所做的所有事情都与之前实现相同,即必需的CLI 参数
$ python main.py
Usage: main.py [OPTIONS] NAME
Try "main.py --help" for help.
Error: Missing argument 'NAME'.
它仍然不是很实用,但它可以正常工作。
并且能够使用以下方式声明必需的CLI 参数
name: Annotated[str, typer.Argument()]
... 与以下方式完全相同
name: str
... 以后会派上用场。
创建可选CLI 参数¶
现在,我们终于迎来了我们的目标,即可选CLI 参数。
要使CLI 参数可选,请使用typer.Argument()
并将不同的“默认值”作为第一个参数传递给typer.Argument()
,例如None
from typing import Optional
import typer
from typing_extensions import Annotated
def main(name: Annotated[Optional[str], typer.Argument()] = None):
if name is None:
print("Hello World!")
else:
print(f"Hello {name}")
if __name__ == "__main__":
typer.run(main)
现在我们有
name: Annotated[Optional[str], typer.Argument()] = None
由于我们使用的是typer.Argument()
,Typer将知道这是一个CLI 参数(无论它是必需的还是可选的)。
提示
通过使用Optional
,您的编辑器将能够知道该值可能为None
,并且如果执行假设它为str
的操作(如果为None
则会中断),它将能够向您发出警告。
查看帮助
// First check the help
$ python main.py --help
Usage: main.py [OPTIONS] [NAME]
Arguments:
[NAME]
Options:
--help Show this message and exit.
提示
请注意,NAME
仍然是一个CLI 参数,它显示在“Usage: main.py
...”中。
另请注意,现在[NAME]
周围有括号(“[
”和“]
”)(之前只是NAME
),表示它是可选的,而不是必需的。
现在运行并测试它
// With no CLI argument
$ python main.py
Hello World!
// With one optional CLI argument
$ python main.py Camila
Hello Camila
提示
请注意,这里的“Camila
”是一个可选的CLI 参数,而不是CLI 选项,因为我们没有使用“--name Camila
”之类的选项,而是直接将“Camila
”传递给程序。
备选(旧)typer.Argument()
作为默认值¶
Typer 还支持另一种较旧的备用语法,用于声明带有附加元数据的CLI 参数。
你可以使用 typer.Argument()
作为默认值,而不是使用 Annotated
import typer
def main(name: str = typer.Argument()):
print(f"Hello {name}")
if __name__ == "__main__":
typer.run(main)
提示
如果可能,最好使用 Annotated
版本。
之前,由于 name
没有默认值,因此在 Python 术语中,它将成为 Python 函数的必需参数。
当使用 typer.Argument()
作为默认值时,Typer 会执行相同操作,并使其成为必需CLI 参数。
我们将其更改为
name: str = typer.Argument()
但现在,由于 typer.Argument()
是函数参数的“默认值”,这意味着“它不再是必需的”(在 Python 术语中)。
由于我们不再有 Python 函数默认值(或其不存在)来判断某个内容是否必需以及默认值是什么,因此 typer.Argument()
会接收一个第一个参数 default
,该参数具有定义该默认值或使其成为必需参数的相同目的。
不向 default
参数传递任何值与将其标记为必需是一样的。但你也可以通过将 ...
传递给 default
参数(传递给 typer.Argument(default=...)
)来明确地将其标记为必需。
name: str = typer.Argument(default=...)
信息
如果你之前没有见过 ...
:它是一个特殊单值,它是 Python 的一部分,称为“省略号”。
import typer
def main(name: str = typer.Argument(default=...)):
print(f"Hello {name}")
if __name__ == "__main__":
typer.run(main)
同样,你可以通过传递不同的 default
值(例如 None
)使其变为可选。
from typing import Optional
import typer
def main(name: Optional[str] = typer.Argument(default=None)):
if name is None:
print("Hello World!")
else:
print(f"Hello {name}")
if __name__ == "__main__":
typer.run(main)
由于传递给 typer.Argument(default=None)
的第一个参数(新的“默认”值)是 None
,因此 Typer 知道这是一个可选CLI 参数,如果在命令行中调用它时未提供值,它将具有 None
的默认值。
default
参数是第一个参数,因此你可能会看到传递值而不显式使用 default=
的代码,例如
name: str = typer.Argument(...)
...或类似
name: str = typer.Argument(None)
...但同样,如果可能,请尝试使用 Annotated
,这样你的 Python 代码在 Typer 中的含义将与 Python 中的含义相同,并且你不必记住任何这些细节。