跳至内容

可选 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 中的含义相同,并且你不必记住任何这些细节。