Following system colour scheme - Python 增强提案 Selected dark colour scheme - Python 增强提案 Selected light colour scheme - Python 增强提案

Python 增强提案

PEP 747 – TypeExpr:类型表达式的类型提示

作者:
David Foster <david at dafoster.net>
赞助人:
Jelle Zijlstra <jelle.zijlstra at gmail.com>
讨论列表:
Discourse 帖子
状态:
草案
类型:
标准跟踪
主题:
类型提示
创建日期:
2024年5月27日
Python 版本:
3.14
历史记录:
2024年4月19日, 2024年5月4日, 2024年6月17日

目录

摘要

PEP 484 定义了type[C] 的表示法,其中C 是一个类,用于引用作为C 的子类型的类对象。它明确不允许type[C] 引用任意类型表达式 对象,例如运行时对象str | None,即使C 是一个无界的TypeVar[1] 在不需要该限制的情况下,本 PEP 提出了一种新的表示法TypeExpr[T],其中T 是一个类型,用于引用作为T 的子类型的类对象或其他一些类型表达式对象,允许引用任何类型的类型。

本 PEP 不会更改 Python 语法。预期仅由静态和运行时类型检查器强制执行TypeExpr[] 的正确用法,并且 Python 本身在运行时无需强制执行。

动机

引入TypeExpr 允许对类型表达式进行操作的新型元编程函数进行类型注释,并被类型检查器理解。

例如,以下是一个函数,用于检查一个值是否可以分配给特定类型的变量,如果可以,则返回原始值

def trycast[T](typx: TypeExpr[T], value: object) -> T | None: ...

使用TypeExpr[] 和类型变量T 使得此函数的返回类型可以受运行时传递的typx 值的影响,这非常强大。

以下是一个函数,用于检查一个值是否可以分配给特定类型的变量,如果可以,则返回True(作为特殊的TypeIs[] 布尔值 [2]

def isassignable[T](value: object, typx: TypeExpr[T]) -> TypeIs[T]: ...

TypeExpr[]TypeIs[] 结合使用,使类型检查器能够根据传入的类型表达式适当地缩小返回类型

request_json: object = ...
if isassignable(request_json, MyTypedDict):
    assert_type(request_json, MyTypedDict)  # type is narrowed!

isassignable 函数实现了一种增强版的isinstance 检查,这对于检查从 JSON 解码的值是否符合嵌套TypedDict、列表、联合、Literal 和其他类型的特定结构很有用。这种检查在PEP 589 中有所提及,但在没有类似于TypeExpr[] 的表示法的情况下,当时无法实现。

为什么不能使用type[]

您可能会认为可以将上面定义的示例函数设为接受type[C](这是一种已经存在的语法),而不是TypeExpr[T]。但是,如果您这样做,则某些类型表达式(如str | None)——它们不是类对象,因此在运行时不是type——将被拒绝

# NOTE: Uses a type[C] parameter rather than a TypeExpr[T]
def trycast_type[C](typ: type[C], value: object) -> T | None: ...

trycast_type(str, 'hi')  # ok; str is a type
trycast_type(Optional[str], 'hi')  # ERROR; Optional[str] is not a type
trycast_type(str | int, 'hi')  # ERROR; (str | int) is not a type
trycast_type(MyTypedDict, dict(value='hi'))  # questionable; accepted by mypy 1.9.0

为了解决这个问题,可以扩展type[] 以包含TypeExpr 允许的其他值。但是,这样做会失去type[] 当前拼写类对象的能力,该类对象始终支持实例化和isinstance 检查,这与任意类型表达式对象不同。因此,建议将TypeExpr 作为新的表示法。

有关我们为什么不只是扩展type[T] 以接受所有类型表达式的更详细说明,请参阅扩展 type[C] 以支持所有类型表达式

受益于 TypeExpr 的常见函数类型

对各种 Python 库的调查 揭示了几种常见的已定义函数类型,这些函数类型将受益于TypeExpr[]

  • 可赋值性检查器
    • 返回一个值是否可以赋值给类型表达式。如果是,则还将值类型缩小以匹配类型表达式。
    • 模式 1:def isassignable[T](value: object, typx: TypeExpr[T]) -> TypeIs[T]
    • 模式 2:def ismatch[T](value: object, typx: TypeExpr[T]) -> TypeGuard[T]
    • 示例:beartype.is_bearable、trycast.isassignable、typeguard.check_type、xdsl.isa
  • 转换器
    • 如果一个值可以赋值给(或可强制转换为)类型表达式,则转换器将值缩小到(或强制转换为)该类型表达式。否则,它将引发异常。
    • 模式 1:def convert[T](value: object, typx: TypeExpr[T]) -> T
    • 模式 2
      class Converter[T]:
          def __init__(self, typx: TypeExpr[T]) -> None: ...
          def convert(self, value: object) -> T: ...
      

调查还发现了一些检查函数,这些函数使用普通object 作为输入来接受注解表达式,这些函数将不会通过将这些输入标记为TypeExpr[] 来获得功能

  • 通用检查操作
    • 模式:def get_annotation_info(maybe_annx: object) -> object
    • 示例:typing.{get_originget_args}、typing_inspect。{is_*_type、get_origin、get_parameters}

基本原理

在 PEP 出现之前,已经使用了一些定义来描述不同类型的类型注解

+----------------------------------+
| +------------------------------+ |
| | +-------------------------+  | |
| | | +---------------------+ |  | |
| | | | Class object        | |  | | = type[C]
| | | +---------------------+ |  | |
| | | Type expression object  |  | | = TypeExpr[T]  <-- new!
| | +-------------------------+  | |
| | Annotation expression object | |
| +------------------------------+ |
| Object                           | = object
+----------------------------------+
  • 类对象,拼写为type[C],支持isinstance 检查并且是可调用的。
    • 示例:intstrMyClass
  • 类型表达式 包括任何描述类型的类型注解。
    • 示例:list[int]MyTypedDictint | strLiteral['square']、任何类对象
  • 注解表达式 包括任何类型注解,包括仅在特定上下文中有效的类型注解。
    • 示例:Final[int]Required[str]ClassVar[str]、任何类型表达式

TypeExpr 与上述列表中现有的定义 - *类型表达式* - 保持一致,以避免引入另一组 Python 类型用户需要考虑的类型注解子集。

TypeExpr 特别与 *类型表达式* 保持一致,因为类型表达式已用于参数化类型变量,这些变量与 TypeIsTypeGuard 结合使用,以实现 动机 中提到的引人注目的示例。

TypeExpr 未与 *注解表达式* 保持一致,原因在 被拒绝的想法 » 接受任意注解表达式 中给出。

规范

TypeExpr 值表示一个 类型表达式,例如 str | Nonedict[str, int]MyTypedDictTypeExpr 类型写成 TypeExpr[T],其中 T 是一个类型或类型变量。它也可以不带括号地写成 TypeExpr,这与 TypeExpr[Any] 的处理方式相同。

使用 TypeExprs

TypeExpr 是一种新型的类型表达式,可以在任何类型表达式有效的上下文中使用,例如函数参数类型、返回类型或变量类型。

def is_union_type(typx: TypeExpr) -> bool: ...  # parameter type
def union_of[S, T](s: TypeExpr[S], t: TypeExpr[T]) \
    -> TypeExpr[S | T]: ...  # return type
STR_TYPE: TypeExpr = str  # variable type

但是请注意,分配类型表达式字面量的 *未注释* 变量不会被类型检查器推断为 TypeExpr 类型,因为 PEP 484 保留该语法用于定义类型别名

  • STR_TYPE = str  # OOPS; treated as a type alias!
    

如果希望类型检查器在裸赋值中识别类型表达式字面量,则需要显式声明赋值目标具有 TypeExpr 类型。

  • STR_TYPE: TypeExpr = str
    
  • STR_TYPE: TypeExpr
    STR_TYPE = str
    
  • 可以,但不鼓励。
    STR_TYPE = str  # type: TypeExpr  # the type comment is significant
    

TypeExpr 值可以像普通值一样传递和赋值。

def swap1[S, T](t1: TypeExpr[S], t2: TypeExpr[T]) -> tuple[TypeExpr[T], TypeExpr[S]]:
    t1_new: TypeExpr[T] = t2  # assigns a TypeExpr value to a new annotated variable
    t2_new: TypeExpr[S] = t1
    return (t1_new, t2_new)

def swap2[S, T](t1: TypeExpr[S], t2: TypeExpr[T]) -> tuple[TypeExpr[T], TypeExpr[S]]:
    t1_new = t2  # assigns a TypeExpr value to a new unannotated variable
    t2_new = t1
    assert_type(t1_new, TypeExpr[T])
    assert_type(t2_new, TypeExpr[S])
    return (t1_new, t2_new)

# NOTE: A more straightforward implementation would use isinstance()
def ensure_int(value: object) -> None:
    value_type: TypeExpr = type(value)  # assigns a type (a subtype of TypeExpr)
    assert value_type == int

TypeExpr 值

类型为 TypeExpr[T] 的变量,其中 T 是一个类型,可以保存任何 **类型表达式对象** - 在运行时评估 类型表达式 的结果 - 它是 T 的子类型。

不完整的表达式,例如不拼写类型的裸 OptionalUnion,不是 TypeExpr 值。

TypeExpr[...] 本身就是一个 TypeExpr 值。

OPTIONAL_INT_TYPE: TypeExpr = TypeExpr[int | None]  # OK
assert isassignable(int | None, OPTIONAL_INT_TYPE)

TypeExpr[] 值包括 *所有* 类型表达式,包括一些 **非通用类型表达式**,这些表达式在所有注解上下文中都不有效。特别是

  • Self(仅在某些上下文中有效)
  • TypeGuard[...](仅在某些上下文中有效)
  • TypeIs[...](仅在某些上下文中有效)

显式 TypeExpr 值

语法 TypeExpr(T)(带括号)可用于显式拼写 TypeExpr[T] 值。

NONE = TypeExpr(None)
INT1 = TypeExpr('int')  # stringified type expression
INT2 = TypeExpr(int)

在运行时,TypeExpr(...) 可调用对象会将其单个参数原样返回。

隐式 TypeExpr 值

历史上,静态类型检查器只需要在期望类型表达式的上下文中识别 *类型表达式*。现在,*类型表达式对象* 也必须在期望值表达式的上下文中识别。

静态类型检查器已经识别 **类对象**(type[C])。

  • 作为值表达式,C 的类型为 type[C],对于 C 的以下每个值:
    • name(其中 name 必须引用有效的范围内类、类型别名或 TypeVar)
    • name '[' ... ']'
    • <type> '[' ... ']'

以下 **未参数化类型表达式** 可以明确识别:

  • 作为值表达式,X 的类型为 TypeExpr[X],对于 X 的以下每个值:
    • <Any>
    • <Self>
    • <LiteralString>
    • <NoReturn>
    • <Never>

**None**:类型表达式 NoneNoneType)与值 None 模棱两可,因此必须使用显式的 TypeExpr(...) 语法。

  • 作为值表达式,TypeExpr(None) 的类型为 TypeExpr[None]
  • 作为值表达式,None 继续具有 None 类型。

以下 **参数化类型表达式** 可以明确识别:

  • 作为值表达式,X 的类型为 TypeExpr[X],对于 X 的以下每个值:
    • <Literal> '[' ... ']'
    • <Optional> '[' ... ']'
    • <Union> '[' ... ']'
    • <Callable> '[' ... ']'
    • <tuple> '[' ... ']'
    • <TypeGuard> '[' ... ']'
    • <TypeIs> '[' ... ']'

**Annotated**:类型表达式 Annotated[...] 与注解表达式 Annotated[...] 模棱两可,因此必须根据其参数类型进行区分。

  • 作为值表达式,如果 x 的类型为 type[C],则 Annotated[x, ...] 的类型为 type[C]
  • 作为值表达式,如果 x 的类型为 TypeExpr[T],则 Annotated[x, ...] 的类型为 TypeExpr[T]
  • 作为值表达式,如果 x 的类型不是 type[C]TypeExpr[T],则 Annotated[x, ...] 的类型为 object

**Union**:类型表达式 T1 | T2 与值 int1 | int2set1 | set2dict1 | dict2 等模棱两可,因此必须使用显式的 TypeExpr(...) 语法。

  • if isassignable(value, TypeExpr(int | str)): ...
    
  • if isassignable(value, int | str): ...
    

未来的 PEP 可能能够直接将值表达式 T1 | T2 识别为隐式 TypeExpr 值,并避免使用显式的 TypeExpr(...) 语法,但该工作 目前推迟

**字符串化类型表达式** "T" 与字符串化注解表达式 "T" 和字符串字面量 "T" 均模棱两可,因此必须使用显式的 TypeExpr(...) 语法。

  • 作为值表达式,TypeExpr("T") 的类型为 TypeExpr[T],其中 T 是一个有效的类型表达式。
  • 作为值表达式,"T" 继续具有 Literal["T"] 类型。

目前不存在其他类型的类型表达式。

引入的新类型表达式应定义如何在值表达式上下文中识别它们。

Literal[] TypeExprs

Literal[...] 类型的 value *不被* 视为可分配给 TypeExpr 变量,即使其所有成员都拼写有效的类型,因为类型表达式中不允许动态值。

STRS_TYPE_NAME: Literal['str', 'list[str]'] = 'str'
STRS_TYPE: TypeExpr = STRS_TYPE_NAME  # ERROR: Literal[] value is not a TypeExpr

但是,Literal[...] 本身仍然是 TypeExpr

DIRECTION_TYPE: TypeExpr[Literal['left', 'right']] = Literal['left', 'right']  # OK

TypeExprs 的静态与运行时表示

在源文件中静态出现的 TypeExpr 值在运行时可能会被规范化为不同的表示形式。例如,基于字符串的前向引用在运行时会被规范化为某些上下文中 ForwardRef 实例:[4]

>>> IntTree = list[typing.Union[int, 'IntTree']]
>>> IntTree
list[typing.Union[int, ForwardRef('IntTree')]]

TypeExpr 的运行时表示被视为可能会随时间推移而更改的实现细节,因此静态类型检查器不需要识别它们。

INT_TREE: TypeExpr = ForwardRef('IntTree')  # ERROR: Runtime-only form

希望将类型表达式的运行时表示分配给 TypeExpr[] 变量的运行时类型检查器必须使用 cast() 以避免来自静态类型检查器的错误。

INT_TREE = cast(TypeExpr, ForwardRef('IntTree'))  # OK

子类型化

是否可以将 TypeExpr 值从一个变量分配到另一个变量由以下规则确定:

与 type 的关系

TypeExpr[] 在其参数类型中是协变的,就像 type[] 一样。

  • TypeExpr[T1]TypeExpr[T2] 的子类型,当且仅当 T1T2 的子类型。
  • type[C1]TypeExpr[C2] 的子类型,当且仅当 C1C2 的子类型。

未参数化的 type 可以赋值给未参数化的 TypeExpr,反之则不行。

  • type[Any] 可以赋值给 TypeExpr[Any]。(但反之则不行。)

与 object 的关系

TypeExpr[] 是一种 object,就像 type[] 一样。

  • 对于任何 TTypeExpr[T] 都是 object 的子类型。

T 是一个类型变量时,TypeExpr[T] 被假定具有 object 的所有属性和方法,并且不可调用。

与 isinstance() 和 issubclass() 的交互

TypeExpr 特殊形式不能用作 isinstancetype 参数。

>>> isinstance(str, TypeExpr)
TypeError: typing.TypeExpr cannot be used with isinstance()

>>> isinstance(str, TypeExpr[str])
TypeError: isinstance() argument 2 cannot be a parameterized generic

TypeExpr 特殊形式不能用作 issubclass 的任何参数。

>>> issubclass(TypeExpr, object)
TypeError: issubclass() arg 1 must be a class

>>> issubclass(object, TypeExpr)
TypeError: typing.TypeExpr cannot be used with issubclass()

标准库中受影响的签名

更改的签名

以下与类型表达式相关的签名引入了 TypeExpr,之前在这些位置使用的是 objectAny

  • typing.cast
  • typing.assert_type

未更改的签名

以下与注解表达式相关的签名继续使用 object 并且保持不变。

  • typing.get_origin
  • typing.get_args

以下与类对象相关的签名继续使用 type 并且保持不变。

  • builtins.isinstance
  • builtins.issubclass
  • builtins.type

typing.get_type_hints(..., include_extras=False) 在 Python 3.12 中几乎只返回类型表达式,去除了大多数类型限定符(Required, NotRequired, ReadOnly, Annotated),但目前保留了一些仅允许在注解表达式中使用的类型限定符(ClassVar, Final, InitVar, Unpack)。将来可能希望更改此函数的行为,以去除这些限定符并真正返回类型表达式,尽管本 PEP 目前没有提出这些更改。

  • typing.get_type_hints(..., include_extras=False)
    • 几乎只返回类型表达式,但并非完全如此。
  • typing.get_type_hints(..., include_extras=True)
    • 返回注解表达式。

向后兼容性

之前没有定义在值表达式上下文中识别类型表达式对象的规则,因此静态类型检查器 在分配给此类对象的类型方面有所不同。现有的操作类型表达式对象的程序在将其作为普通 object 值操作方面已经受到限制,并且此类程序不应该随着 新定义的规则 而中断。

如何教授

通常,当在 Python 中使用类型注解时,您关心的是定义允许传递给函数参数、由函数返回或存储在变量中的值的形状。

              parameter type   return type
              |                |
              v                v
def plus(n1: int, n2: int) -> int:
    sum: int = n1 + n2
          ^
          |
          variable type

    return sum

但是类型注解本身是 Python 中的有效值,可以分配给变量并像程序中的任何其他数据一样进行操作。

 a variable                    a type
 |                             |
 v                             v
MAYBE_INT_TYPE: TypeExpr = int | None
                 ^
                 |
                 the type of a type

TypeExpr[] 是您拼写包含描述类型的类型注解对象的变量的类型的写法。

TypeExpr[] 类似于 type[],但 type[] 只能拼写简单的**类对象**,例如 intstrlistMyClass。相比之下,TypeExpr[] 还可以拼写更复杂的类型,包括带有方括号(如 list[int])或管道(如 int | None)的类型,以及包括 AnyLiteralStringNever 等特殊类型。

一个 TypeExpr 变量(maybe_float: TypeExpr)看起来类似于 TypeAlias 定义(MaybeFloat: TypeAlias),但 TypeExpr 只能在期望动态值的地方使用。

  • maybe_float: TypeExpr = float | None
    def sqrt(n: float) -> maybe_float: ...  # ERROR: Can't use TypeExpr value in a type annotation
    
  • 可以,但在 Python 3.12+ 中不鼓励使用。
    MaybeFloat: TypeAlias = float | None
    def sqrt(n: float) -> MaybeFloat: ...
    
  • type MaybeFloat = float | None
    def sqrt(n: float) -> MaybeFloat: ...
    

程序员很少定义自己的接受 TypeExpr 参数或返回 TypeExpr 值的函数。相反,程序员更常见的是将文字类型表达式传递给接受 TypeExpr 输入的现有函数,这些函数是从运行时类型检查器库导入的。

例如,来自 trycast 库的 isassignable 函数可以像 Python 内置的 isinstance 函数一样,用于检查值是否与特定类型的形状匹配。isassignable 将接受任何类型的输入,因为其输入是 TypeExpr。相比之下,isinstance 只接受简单的类对象(type[])作为输入。

  • from trycast import isassignable
    
    if isassignable(some_object, MyTypedDict):  # OK: MyTypedDict is a TypeExpr[]
        ...
    
  • if isinstance(some_object, MyTypedDict):  # ERROR: MyTypedDict is not a type[]
        ...
    

许多其他运行时类型检查器 提供接受 TypeExpr 的有用函数。

高级示例

如果您想编写自己的运行时类型检查器或其他类型的函数,这些函数在运行时将类型作为值进行操作,本节将提供一些有关如何使用 TypeExpr 实现此类函数的示例。

检查 TypeExpr 值

在运行时,TypeExprobject 非常相似,没有定义其他属性或方法。

您可以使用现有的内省函数(如 typing.get_origintyping.get_args)来提取看起来像 Origin[Arg1, Arg2, ..., ArgN] 的类型表达式的组件。

import typing

def strip_annotated_metadata(typx: TypeExpr[T]) -> TypeExpr[T]:
    if typing.get_origin(typx) is typing.Annotated:
        typx = cast(TypeExpr[T], typing.get_args(typx)[0])
    return typx

您还可以使用 isinstanceis 来区分一种类型的表达式与另一种类型的表达式。

import types
import typing

def split_union(typx: TypeExpr) -> tuple[TypeExpr, ...]:
    if isinstance(typx, types.UnionType):  # X | Y
        return cast(tuple[TypeExpr, ...], typing.get_args(typx))
    if typing.get_origin(typx) is typing.Union:  # Union[X, Y]
        return cast(tuple[TypeExpr, ...], typing.get_args(typx))
    if typx in (typing.Never, typing.NoReturn,):
        return ()
    return (typx,)

与类型变量结合

TypeExpr[] 可以由在同一函数定义中其他地方使用的类型变量进行参数化。

def as_instance[T](typx: TypeExpr[T]) -> T | None:
    return typx() if isinstance(typx, type) else None

与 type[] 结合

TypeExpr[]type[] 都可以在同一函数定义中由相同的类型变量进行参数化。

def as_type[T](typx: TypeExpr[T]) -> type[T] | None:
    return typx if isinstance(typx, type) else None

与 TypeIs[] 和 TypeGuard[] 结合

参数化 TypeExpr[] 的类型变量也可以由同一函数定义中的 TypeIs[] 使用。

def isassignable[T](value: object, typx: TypeExpr[T]) -> TypeIs[T]: ...

count: int | str = ...
if isassignable(count, int):
    assert_type(count, int)
else:
    assert_type(count, str)

或由同一函数定义中的 TypeGuard[] 使用。

def isdefault[T](value: object, typx: TypeExpr[T]) -> TypeGuard[T]:
    return (value == typx()) if isinstance(typx, type) else False

value: int | str = ''
if isdefault(value, int):
    assert_type(value, int)
    assert 0 == value
elif isdefault(value, str):
    assert_type(value, str)
    assert '' == value
else:
    assert_type(value, int | str)

接受所有 TypeExprs 时的挑战

一个以任意 TypeExpr 作为输入的函数必须支持各种可能的类型表达式,并且不容易编写。此类函数面临的一些挑战包括:

  • 每个新的 Python 版本都会引入越来越多的类型特殊形式,这些形式必须被识别,并且每个形式都需要特殊的处理。
  • 字符串化的类型注解 [5](如 'list[str]')必须被解析(解析为类似 typing.List[str] 的东西)才能进行内省。
    • 在实践中,字符串化的类型注解非常难以在运行时可靠地处理,因此运行时类型检查器可能会选择根本不支持它们。
  • 解析类型表达式内部基于字符串的前向引用到实际值通常必须使用 eval(),这很难/不可能以安全的方式使用。
  • 递归类型(如 IntTree = list[typing.Union[int, 'IntTree']])无法完全解析。
  • 支持用户定义的泛型类型(如 Django 的 QuerySet[User])需要用户定义的函数来识别/解析,运行时类型检查器应该为此提供一个注册 API。

参考实现

mypy#9773 实现时,以下将为真。

mypy 类型检查器支持 TypeExpr 类型。

运行时组件的参考实现在 typing_extensions 模块中提供。

被拒绝的想法

扩展 type[C] 以支持所有类型表达式

type设计 为仅用于描述类对象。类对象始终可以用作 isinstance() 的第二个参数,并且通常可以通过调用它来实例化。

TypeExpr 另一方面通常以某种方式由用户进行内省,不一定可以直接实例化,也不一定可以直接在常规的 isinstance() 检查中使用。

可以将type扩展以包含TypeExpr允许的附加值,但这会降低在使用type时对用户意图的清晰度。不同的概念和使用模式;不同的拼写。

接受任意注解表达式

某些类型特殊形式可以在某些但不是所有注释上下文中使用

例如,Final[]可以用作变量类型,但不能用作参数类型或返回类型

some_const: Final[str] = ...  # OK

def foo(not_reassignable: Final[object]): ...  # ERROR: Final[] not allowed here

def nonsense() -> Final[object]: ...  # ERROR: Final[] not meaningful here

TypeExpr[T]不允许匹配此类注释表达式,因为它不清楚此类表达式在位置T被类型变量参数化意味着什么

def ismatch[T](value: object, typx: TypeExpr[T]) -> TypeGuard[T]: ...

def foo(some_arg):
    if ismatch(some_arg, Final[int]):  # ERROR: Final[int] is not a TypeExpr
        reveal_type(some_arg)  # ? NOT Final[int], because invalid for a parameter

希望对所有类型的注释表达式(包括不是TypeExpr的表达式)进行操作的函数,可以继续将此类输入作为object参数接受,就像它们今天必须做的那样。

仅接受通用类型表达式

此 PEP 的早期草案仅允许TypeExpr[]匹配在所有上下文中有效的类型表达式的子集,不包括非通用类型表达式。但是这样做实际上会创建一个Python类型用户必须理解的注释表达式的新的子集,这要添加到“类对象”、“类型表达式”和“注释表达式”之间所有现有的区别之上。

为了避免引入另一个每个人都必须学习的概念,此提案只是将TypeExpr[]四舍五入以完全匹配“类型表达式”的现有定义。

支持对类型表达式进行模式匹配

有人断言某些函数可能希望在其签名中对类型表达式的内部进行模式匹配。

一个用例是允许函数显式枚举其支持的所有特定类型的表达式作为输入。考虑以下可能的模式匹配语法

@overload
def checkcast(typx: TypeExpr[AT=Annotated[T, *Anns]], value: str) -> T: ...
@overload
def checkcast(typx: TypeExpr[UT=Union[*Ts]], value: str) -> Union[*Ts]: ...
@overload
def checkcast(typx: type[C], value: str) -> C: ...
# ... (more)

在实际应用中观察到的所有在概念上采用TypeExpr[]的函数通常都试图支持所有类型的表达式,因此枚举特定子集似乎没有价值。

此外,上述语法不够精确,无法完全描述实际应用中典型函数的实际输入约束。例如,许多函数识别未字符串化的类型表达式,如list[Movie],但可能无法识别具有字符串化子组件的类型表达式,如list['Movie']

对类型表达式内部进行模式匹配的第二个用例是显式匹配Annotated[]表单以提取内部类型参数并去除元数据

def checkcast(
    typx: TypeExpr[T] | TypeExpr[AT=Annotated[T, *Anns]],
    value: object
) -> T:

但是,Annotated[T, metadata]无论如何都已被视为等同于T。明确说明此行为没有任何额外的价值。上面的示例可以更直接地写成等效的

def checkcast(typx: TypeExpr[T], value: object) -> T:

将 (T1 | T2) 识别为隐式 TypeExpr 值

如果像int | str这样的值表达式可以被识别为隐式的TypeExpr值并直接在期望使用TypeExpr的上下文中使用,那将是很好的。但是,要实现这一点,需要对类型检查器对|运算符使用的规则进行更改。这些规则目前定义不明确,需要首先明确这些规则,然后才能对其进行更改。在撰写本文时,PEP 作者没有足够的动力来承担该规范工作。

脚注


来源:https://github.com/python/peps/blob/main/peps/pep-0747.rst

上次修改时间:2024-08-20 10:29:32 GMT