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

Posted

技术标签:

【中文标题】我应该如何使用 Optional 类型提示?【英文标题】:How should I use the Optional type hint? 【发布时间】:2019-01-13 13:19:57 【问题描述】:

我正在尝试了解如何使用Optional 类型提示。从PEP-484,我知道我可以将Optional 用作def test(a: int = None) 或者def test(a: Union[int, None])def test(a: Optional[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[]

【问题讨论】:

使用 Optional 或 Union[..., None] 而不是 a: list = None 有什么好处?那个语法不是已经自我解释了吗? @gohu - 你是对的。对于关键字 args,mypy 和 IDE 都能够假定显而易见并自动将它们视为 Optional。请参阅下面的答案。 【参考方案1】:

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[] 或在Union[] 上添加None 在技术上没有区别。所以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

【讨论】:

@MartijnPieters 我们不需要从输入中导入DictList 并写入Optional[Dict]Optional[List] 而不是Optional[dict]... @Alireza 是的,我已经在回答中声明了这一点。寻找:旁注:但是,您要避免在类型提示中使用标准库容器类型,因为您无法说明它们必须包含哪些类型 关于listdict 的旁注的重要性怎么强调都不为过。在查阅了官方 Python 文档之后,直到这次讨论,我才意识到这是事实。为你们强调这一点干杯。 有趣的是,当 Python 3.10 引入新的联合运算符 (PEP 604) 时,首选方式将回到 Union[str, int, None],现在以一种新奇的方式编写为 @987654383 @,无需导入打字模块。 如果你添加from __future__ import annotations,你可以在Python 3.7+上使用dict[X]list[X]【参考方案2】:

直接来自mypy typing module docs。

“Optional[str] 只是 Union[str, None] 的简写或别名。它的存在主要是为了方便函数签名看起来更简洁。”

【讨论】:

【参考方案3】:

虽然接受的答案是正确答案,但需要注意的另一件事是,kwargs 的上下文中,Optional[...]Union[..., None] 都是多余且不必要的。如果您立即将您的 kwarg 设置为 None,那么 mypy 和 IDE 都假定显而易见并且自动将 arg 视为 Optional[...]

IDE:

mypy:

对于变量和方法/函数返回值,Optional[...] 仍然是必要的,但是,因为mypy 在这些情况下无法知道自动假设任何内容。

【讨论】:

【参考方案4】:

在 Python 3.9 之前,如果您想提示 可空 值,您有两种选择:

import typing

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

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

从 Python 3.9 开始,您不需要使用输入模块:

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

【讨论】:

所以,none 不是str,所以你还需要optional @Mikhail def foo(bar: str = None): 等价于 def foo(bar: typing.Optional[str]): 对于前者,当您使用 bar: some_type = None 时,这是在推断使用 str 的选项,否则参数将为 None。但是,如果您有多种类型的参数,可选可能是要走的路。例如:def foo(bar: typing.Optional[str, list, None]): @Xammax 嗯,这很奇怪。你是说def foo(bar: str = None): 实际上是def foo(bar: Optional[str] = None):?你如何表明该栏不能是None @Xammax 嗯,回到原来的问题,OP 的回答是错误的,因为foo(bar: str = None) 告诉类型提示机制bar 不能是None(只有str)但是然后立即将None 传递给它。我们知道,None 和空的str 是不同的类型。 Python 3.9 “移除”的是需要使用typing 来表达Union[str, None]Optional[str] - 现在可以使用str | None。可能收到None 的参数应该仍然这样明确地注释,只是这样做的方式现在更符合人体工程学。 Omitting the None was allowed previously but is being phased out.

以上是关于我应该如何使用 Optional 类型提示?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Python 中创建自己的“参数化”类型(如 `Optional[T]`)?

如何在 Long 数据类型变量中设置 Optional<Long> 数据类型变量值? [关闭]

你如何对 optional 的 optional 执行可选绑定?

如何理解Swift中Optional的 ! 和 ?

如何理解Swift中Optional的 ! 和 ?

为啥不应该在参数中使用 Java 8 的 Optional