我正在尝试理解如何使用可选类型提示。从PEP-484,我知道我可以使用可选的def测试(a: int = None)作为def测试(a:联盟[int, None])或def测试(a:可选[int])。

但是下面的例子呢?

def test(a : dict = None):
    #print(a) ==> {'a': 1234}
    #or
    #print(a) ==> None

def test(a : list = None):
    #print(a) ==> [1,2,3,4, 'a', 'b']
    #or
    #print(a) ==> None

如果Optional[type]似乎意味着与Union[type, None]相同的事情,为什么我应该使用Optional[]呢?


可选[…是联合的缩写[…], None],告诉类型检查器需要特定类型的对象,或者需要None . ...表示任何有效的类型提示,包括复杂的复合类型或多个类型的联合[]。当你有一个默认值为None的关键字参数时,你应该使用Optional。(注意:如果您的目标是Python 3.10或更新版本,PEP 604引入了更好的语法,请参阅下文)。

所以在你的两个例子中,你有dict和list容器类型,但是a关键字参数的默认值也显示为None,所以使用Optional[…]:

from typing import Optional

def test(a: Optional[dict] = None) -> None:
    #print(a) ==> {'a': 1234}
    #or
    #print(a) ==> None

def test(a: Optional[list] = None) -> None:
    #print(a) ==> [1, 2, 3, 4, 'a', 'b']
    #or
    #print(a) ==> None

在联合[]上使用Optional[]和在联合[]中添加None在技术上没有区别。所以Optional[Union[str, int]]和Union[str, int, None]是完全一样的东西。

就我个人而言,在为关键字参数设置类型时,我总是坚持使用Optional[],使用= None来设置默认值,这说明了为什么允许None更好。此外,这也更容易推动欧盟……]部分变成一个单独的类型别名,或者稍后删除Optional[…]如果一个论点成为强制性的。

例如,假设你有

from typing import Optional, Union

def api_function(optional_argument: Optional[Union[str, int]] = None) -> None:
    """Frob the fooznar.

    If optional_argument is given, it must be an id of the fooznar subwidget
    to filter on. The id should be a string, or for backwards compatibility,
    an integer is also accepted.

    """

然后通过将联合[str, int]提取为类型别名来改进文档:

from typing import Optional, Union

# subwidget ids used to be integers, now they are strings. Support both.
SubWidgetId = Union[str, int]


def api_function(optional_argument: Optional[SubWidgetId] = None) -> None:
    """Frob the fooznar.

    If optional_argument is given, it must be an id of the fooznar subwidget
    to filter on. The id should be a string, or for backwards compatibility,
    an integer is also accepted.

    """

将Union[]移动到别名的重构变得非常容易,因为Optional[…]使用]代替Union[str, int, None]。None值毕竟不是一个“subwidget id”,它不是值的一部分,None意味着标记一个值的缺失。

旁注:除非你的代码只支持Python 3.9或更新版本,否则你应该避免在类型提示中使用标准库容器类型,因为你不能说明它们必须包含什么类型。因此,不要使用dict和list,而是使用typing。字典和打字。分别列表。当只读取容器类型时,你也可以接受任何不可变的抽象容器类型;list和tuple是Sequence对象,而dict是Mapping类型:

from typing import Mapping, Optional, Sequence, Union

def test(a: Optional[Mapping[str, int]] = None) -> None:
    """accepts an optional map with string keys and integer values"""
    # print(a) ==> {'a': 1234}
    # or
    # print(a) ==> None

def test(a: Optional[Sequence[Union[int, str]]] = None) -> None:
    """accepts an optional sequence of integers and strings
    # print(a) ==> [1, 2, 3, 4, 'a', 'b']
    # or
    # print(a) ==> None

在Python 3.9及更高版本中,标准容器类型已全部更新,以支持在类型提示中使用它们,请参阅PEP 585。但是,虽然您现在可以使用dict[str, int]或list[Union[int, str]],但您仍然可能希望使用更具表现力的Mapping和Sequence注释来表明函数不会改变内容(它们被视为'只读'),并且函数将分别与作为映射或序列的任何对象一起工作。

Python 3.10在类型提示中引入了|联合运算符,参见PEP 604。而不是Union[str, int]你可以写str | int。与其他类型提示语言一致,在Python 3.10及以上版本中表示可选参数的首选(更简洁)方式现在是Type | None,例如str | None或list | None。


直接从mypy输入模块文档。

可选[str]只是联合[str, None]的缩写或别名。它的存在主要是为了帮助函数签名看起来更简洁。

Python 3.10+更新

现在还可以使用管道操作符。

# Python < 3.10
def get_cars(size: Optional[str]=None):
    pass
# Python 3.10+
def get_cars(size: str|None=None):
    pass

虽然公认的答案是正确的答案,但需要注意的另一件事是,在kwargs的上下文中,两者都是可选的[…]和Union[…],没有]是多余和不必要的。如果你立即将你的kwarg设置为None,那么mypy和ide都假设这是显而易见的,并自动将arg视为Optional[…]。

IDE:

我的:

对于变量和方法/函数返回值,可选[…]]仍然是必要的,然而,因为我不知道,在这种情况下,自动假设任何事情。


在Python 3.9之前,如果你想提示一个可空值,你有两个选项:

import typing

def foo(bar: typing.Optional[str]):
    ....

def foo(bar: typing.Union[str, None]):
    ....

从Python 3.9开始,你不需要使用typing模块:

def foo(bar: str = None):
    ....

注意,从Python 3.10开始,你可以简化你的代码,输入如下:

def foo(
   bar: int | None = None,
   another_bar: Callable[[int, list, float, datetime | None], str],
):

正如已经在一些注释中指出的那样,自Python 3.7以来,已经可以通过__future__导入使用新样式的类型注释:

from __future__ import annotations

def test(a: dict[str, int] | None) -> None:
   ...

至于自动回答这个问题和许多其他通用的最佳实践问题,我强烈建议使用pyupgrade以现代Python风格自动重新格式化您的类型注释和其余代码。对于单个文件,在添加__future__导入后,运行pyupgrade——py37-plus——keep-runtime-typing file.py。

如果您使用Git,那么pyupgrade可以设置为预提交钩子,以便您的代码始终保持现代化。这是我使用的块:

# Upgrade code style to the specified minimum supported Python version.
- repo: https://github.com/asottile/pyupgrade
  rev: v3.3.1  # Adjust this to the latest version of pyupgrade
  hooks:
  - id: pyupgrade
    # Adjust the following to the minimum supported Python version for your project.
    args:
    - --py37-plus
    - --keep-runtime-typing

注意:如果你使用Pydantic、FastAPI或其他依赖于运行时类型的类似工具,——keep-runtime-typing参数是必需的。否则,可以安全地省略此参数。