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: ...
- 示例:pydantic.TypeAdapter(T).validate_python、mashumaro.JSONDecoder(T).decode
- 类型化字段定义
- 模式
class Field[T]: value_type: TypeExpr[T]
- 示例:attrs.make_class、dataclasses.make_dataclass [3]、openapify
- 模式
调查还发现了一些检查函数,这些函数使用普通object
作为输入来接受注解表达式,这些函数将不会通过将这些输入标记为TypeExpr[]
来获得功能
- 通用检查操作
- 模式:
def get_annotation_info(maybe_annx: object) -> object
- 示例:typing.{get_origin、get_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
检查并且是可调用的。- 示例:
int
、str
、MyClass
- 示例:
- 类型表达式 包括任何描述类型的类型注解。
- 示例:
list[int]
、MyTypedDict
、int | str
、Literal['square']
、任何类对象
- 示例:
- 注解表达式 包括任何类型注解,包括仅在特定上下文中有效的类型注解。
- 示例:
Final[int]
、Required[str]
、ClassVar[str]
、任何类型表达式
- 示例:
TypeExpr
与上述列表中现有的定义 - *类型表达式* - 保持一致,以避免引入另一组 Python 类型用户需要考虑的类型注解子集。
TypeExpr
特别与 *类型表达式* 保持一致,因为类型表达式已用于参数化类型变量,这些变量与 TypeIs
和 TypeGuard
结合使用,以实现 动机 中提到的引人注目的示例。
TypeExpr
未与 *注解表达式* 保持一致,原因在 被拒绝的想法 » 接受任意注解表达式 中给出。
规范
TypeExpr
值表示一个 类型表达式,例如 str | None
、dict[str, int]
或 MyTypedDict
。 TypeExpr
类型写成 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
的子类型。
不完整的表达式,例如不拼写类型的裸 Optional
或 Union
,不是 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**:类型表达式 None
(NoneType
)与值 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 | int2
、set1 | set2
、dict1 | 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]
的子类型,当且仅当T1
是T2
的子类型。type[C1]
是TypeExpr[C2]
的子类型,当且仅当C1
是C2
的子类型。
未参数化的 type
可以赋值给未参数化的 TypeExpr
,反之则不行。
type[Any]
可以赋值给TypeExpr[Any]
。(但反之则不行。)
与 object 的关系
TypeExpr[]
是一种 object
,就像 type[]
一样。
- 对于任何
T
,TypeExpr[T]
都是object
的子类型。
当 T
是一个类型变量时,TypeExpr[T]
被假定具有 object
的所有属性和方法,并且不可调用。
与 isinstance() 和 issubclass() 的交互
TypeExpr
特殊形式不能用作 isinstance
的 type
参数。
>>> 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
,之前在这些位置使用的是 object
或 Any
。
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[]
只能拼写简单的**类对象**,例如 int
、str
、list
或 MyClass
。相比之下,TypeExpr[]
还可以拼写更复杂的类型,包括带有方括号(如 list[int]
)或管道(如 int | None
)的类型,以及包括 Any
、LiteralString
或 Never
等特殊类型。
一个 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 值
在运行时,TypeExpr
与 object
非常相似,没有定义其他属性或方法。
您可以使用现有的内省函数(如 typing.get_origin
和 typing.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
您还可以使用 isinstance
和 is
来区分一种类型的表达式与另一种类型的表达式。
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 作者没有足够的动力来承担该规范工作。
脚注
版权
本文档置于公共领域或根据 CC0-1.0-Universal 许可证,以许可证中较宽松者为准。
来源:https://github.com/python/peps/blob/main/peps/pep-0747.rst
上次修改时间:2024-08-20 10:29:32 GMT