python-3.x 我应该如何使用可选类型提示?

e37o9pze  于 2022-12-15  发布在  Python
关注(0)|答案(6)|浏览(162)

我试着理解如何使用Optional类型提示,从PEP-484中,我知道我可以将Optional用作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[]呢?

twh00eeo

twh00eeo1#

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来设置默认值的关键字参数设置类型时,我会坚持 always using 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值不是一个“subwidget id”,它不是值的一部分,None旨在标记值的缺失。
旁注:除非你的代码只需要支持Python 3.9或更新版本,否则你会希望避免在类型提示中使用标准库容器类型,因为你不能说明它们必须包含什么类型。因此,不要使用dictlist,而要分别使用typing.Dicttyping.List。并且,当只从容器类型中 * 阅读 * 时,你也可以接受任何不可变的抽象容器类型;列表和元组是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注解来指示函数不会改变内容(它们被视为“只读”),并且这些函数将分别与作为Map或序列工作的 any 对象一起工作。
Python 3.10引入了|并集运算符到类型提示中,参见PEP 604。你可以写str | int来代替Union[str, int]。与其他类型提示语言一致,在Python 3.10及以上版本中表示可选参数的首选(也更简洁)方式现在是Type | None,例如str | Nonelist | None

3gtaxfhh

3gtaxfhh2#

直接从mypy打字模块文档。
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
ktecyv1j

ktecyv1j3#

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

作者:

对于变量和方法/函数的返回值,Optional[...]仍然是必需的,但是,在这些情况下,mypy不知道自动假设什么。

daupos2t

daupos2t4#

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

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

eimct9ow5#

在Python 3.9之前,如果你想提示一个nullable值,你有两个选择:

import typing

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

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

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

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

k10s72fa6#

正如在几个注解中提到的,从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参数。否则,可以安全地省略此参数。

相关问题