我应该如何使用可选类型提示?

我正在试图理解如何使用Optional类型提示。从pep - 484中,我知道可以将Optional作为def test(a: Union[int, None])def test(a: Optional[int])用于def test(a: int = None)

但是下面的例子呢?

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[]呢?

259013 次浏览

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

所以对于你的两个例子,你有dictlist容器类型,但是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

Union[]上使用Optional[]或只是将None添加到Union[],在技术上没有区别。所以Optional[Union[str, int]]Union[str, int, None]是完全一样的东西。

就我个人而言,在为一个使用= None设置默认值的关键字参数设置类型时,我会坚持使用总是使用Optional[],这说明了为什么更允许使用None。此外,它可以更容易地将Union[...]部分移动到单独的类型别名中,或者稍后在参数成为强制参数时删除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.


"""

然后通过将Union[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值毕竟不是“子部件id”,它不是值的一部分,None意味着标记一个值的缺失。

旁注:除非你的代码只支持Python 3.9或更新版本,否则你应该避免在类型提示中使用标准库容器类型,因为你不能说明它们必须包含什么类型。因此,使用typing.Dicttyping.List来代替dictlist。当只有阅读来自容器类型时,你也可以接受任何不可变的抽象容器类型;列表和元组是Sequence对象,而dictMapping类型:

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]]时,你仍然可能想要使用更有表达性的MappingSequence注释来表明函数不会改变内容(它们被视为'只读'),并且函数将分别与作为映射或序列的任何对象一起工作。

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

直接从我正在输入模块文档

Optional[str]只是Union[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的上下文中Optional[...]Union[..., None]都是多余和不必要的。如果你立即将你的kwarg设置为None,那么mypy和ide都假设是明显的,自动将参数处理为Optional[...]

IDE:

enter image description here

mypy:

 myypy automatic可选 . b0

在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],
):