PEP 747 – 注解类型形式
- 作者:
- David Foster <david at dafoster.net>, Eric Traut <erictr at microsoft.com>
- 发起人:
- Jelle Zijlstra <jelle.zijlstra at gmail.com>
- 讨论至:
- Discourse 帖子
- 状态:
- 草案
- 类型:
- 标准跟踪
- 主题:
- 类型标注
- 创建日期:
- 2024年5月27日
- Python 版本:
- 3.15
- 发布历史:
- 2024年4月19日, 2024年5月4日, 2024年6月17日
摘要
类型表达式 提供了一种在 Python 类型系统中指定类型的标准化方式。当类型表达式在运行时被评估时,结果的 类型形式对象 编码了类型表达式中提供的信息。这使得运行时类型检查、内省和元编程等各种用例成为可能。
此类用例已大量出现,但目前没有办法准确地注解接受类型形式对象的函数。开发者被迫使用过于宽泛的类型,如 object,这使得某些用例无法实现并普遍降低了类型安全性。本 PEP 通过引入新的特殊形式 typing.TypeForm 来解决此限制。
本 PEP 不对 Python 语法进行任何更改。TypeForm 的正确使用仅由类型检查器强制执行,而不由 Python 运行时强制执行。
动机
操作类型形式对象的函数必须理解类型表达式的细节是如何编码在这些对象中的。例如,int | str、"int | str"、list[int] 和 MyTypeAlias 都是有效的类型表达式,它们分别评估为 types.UnionType、builtins.str、types.GenericAlias 和 typing.TypeAliasType 的实例。
目前没有办法向类型检查器指示函数接受类型形式对象并知道如何处理它们。TypeForm 解决了此限制。例如,这是一个检查值是否可赋值给指定类型,如果不可赋值则返回 None 的函数
def trycast[T](typx: TypeForm[T], value: object) -> T | None: ...
TypeForm 和类型变量 T 的使用描述了传递给参数 typx 的类型形式与函数的返回类型之间的关系。
TypeForm 也可以与 TypeIs 结合使用来定义自定义类型收窄行为
def isassignable[T](value: object, typx: TypeForm[T]) -> TypeIs[T]: ...
request_json: object = ...
if isassignable(request_json, MyTypedDict):
assert_type(request_json, MyTypedDict) # Type of variable is narrowed
isassignable 函数实现了一种类似增强版 isinstance 的检查。这对于验证从 JSON 解码的值是否符合嵌套 TypedDict、列表、联合、Literal 或任何其他可以用类型表达式描述的类型形式的特定结构很有用。这种检查在 PEP 589 中有所提及,但如果没有 TypeForm 则无法实现。
为什么不是 type[C]?
有人可能会认为 type[C] 足以满足这些用例。但是,只有类对象(builtins.type 类的实例)才能赋值给 type[C]。许多类型形式对象不符合此要求
def trycast[T](typx: type[T], value: object) -> T | None: ...
trycast(str, 'hi') # OK
trycast(Literal['hi'], 'hi') # Type violation
trycast(str | None, 'hi') # Type violation
trycast(MyProtocolClass, obj) # Type violation
TypeForm 用例
对 Python 库的调查 揭示了几类将受益于 TypeForm 的函数
- 可赋值性检查器
- 确定值是否可赋值给指定类型
- 模式 1:
def is_assignable[T](value: object, typx: TypeForm[T]) -> TypeIs[T] - 模式 2:
def is_match[T](value: object, typx: TypeForm[T]) -> TypeGuard[T] - 示例: beartype.is_bearable, trycast.isassignable, typeguard.check_type, xdsl.isa
- 转换器
- 如果一个值可赋值给(或可强制转换为)指定类型,则 转换器 返回该值收窄为(或强制转换为)该类型。否则,会引发异常。
- 模式 1
def convert[T](value: object, typx: TypeForm[T]) -> T
- 模式 2
class Converter[T]: def __init__(self, typx: TypeForm[T]) -> None: ... def convert(self, value: object) -> T: ...
- 示例: pydantic.TypeAdapter(T).validate_python, mashumaro.JSONDecoder(T).decode
- 类型化字段定义
- 模式
class Field[T]: value_type: TypeForm[T]
- 示例: attrs.make_class, dataclasses.make_dataclass [3], openapify
- 模式
该调查还发现了一些接受运行时类型形式作为输入的内省函数。目前,这些函数用 object 进行注解
- 一般内省操作
- 模式:
def get_annotation_info(typx: object) -> object - 示例: typing.{get_origin, get_args}, typing_inspect.{is_*_type, get_origin, get_parameters}
- 模式:
这些函数接受从任意注解表达式而不是仅仅是类型表达式评估的值,因此它们不能被修改以使用 TypeForm。
规范
当类型表达式在运行时被评估时,结果值是一个 类型形式 对象。这个值编码了类型表达式中提供的信息,并且它表示该类型表达式所描述的类型。
TypeForm 是一种特殊形式,当在类型表达式中使用时,它描述了一组类型形式对象。它接受一个类型参数,该参数必须是 有效的类型表达式。TypeForm[T] 描述了所有表示类型 T 或 可赋值给 T 的类型的所有类型形式对象。例如,TypeForm[str | None] 描述了所有表示可赋值给 str | None 的类型的类型形式对象
ok1: TypeForm[str | None] = str | None # OK
ok2: TypeForm[str | None] = str # OK
ok3: TypeForm[str | None] = None # OK
ok4: TypeForm[str | None] = Literal[None] # OK
ok5: TypeForm[str | None] = Optional[str] # OK
ok6: TypeForm[str | None] = "str | None" # OK
ok7: TypeForm[str | None] = Any # OK
err1: TypeForm[str | None] = str | int # Error
err2: TypeForm[str | None] = list[str | None] # Error
根据这个相同的定义,TypeForm[object] 描述了一个表示类型 object 或任何可赋值给 object 的类型的类型形式对象。由于 Python 类型系统中的所有类型都可以赋值给 object,因此 TypeForm[object] 描述了从所有有效类型表达式评估的所有类型形式对象的集合。
TypeForm[Any] 描述了一种 TypeForm 类型,其类型参数是静态未知但又是有效的类型形式对象。因此,它可赋值给任何其他 TypeForm 类型,并且任何其他 TypeForm 类型也可赋值给它(因为 Any 可赋值给任何类型,反之亦然)。
类型表达式 TypeForm,如果没有提供类型参数,则等同于 TypeForm[Any]。
隐式 TypeForm 评估
当静态类型检查器遇到有效的类型表达式时,如果该表达式描述的类型可赋值给 T,则该表达式的评估类型应可赋值给 TypeForm[T]。
例如,如果静态类型检查器遇到表达式 str | None,它通常会将其类型评估为 UnionType,因为它会产生一个 types.UnionType 实例的运行时值。但是,由于此表达式是有效的类型表达式,它也可以赋值给类型 TypeForm[str | None]
v1_actual: UnionType = str | None # OK
v1_type_form: TypeForm[str | None] = str | None # OK
v2_actual: type = list[int] # OK
v2_type_form: TypeForm = list[int] # OK
在类型表达式中允许使用 Annotated 特殊形式,因此它也可以出现在可赋值给 TypeForm 的表达式中。与类型规范中关于 Annotated 的规则一致,静态类型检查器可以选择忽略任何它不理解的 Annotated 元数据
v3: TypeForm[int | str] = Annotated[int | str, "metadata"] # OK
v4: TypeForm[Annotated[int | str, "metadata"]] = int | str # OK
包含有效类型表达式的字符串字面量表达式也应可赋值给 TypeForm
v5: TypeForm[set[str]] = "set[str]" # OK
有效类型表达式
类型规范以形式语法的形式定义了类型表达式的语法规则。语义规则作为注释与语法定义一起指定。上下文要求在类型规范中讨论类型表达式中出现的概念的部分中详细说明。例如,特殊形式 Self 只能在类中使用,并且类型变量只能在与有效作用域关联时才能在类型表达式中使用。
有效的类型表达式是遵循类型表达式所有语法、语义和上下文规则的表达式。
非有效的类型表达式不应评估为 TypeForm 类型
bad1: TypeForm = tuple() # Error: Call expression not allowed in type expression
bad2: TypeForm = (1, 2) # Error: Tuple expression not allowed in type expression
bad3: TypeForm = 1 # Non-class object not allowed in type expression
bad4: TypeForm = Self # Error: Self not allowed outside of a class
bad5: TypeForm = Literal[var] # Error: Variable not allowed in type expression
bad6: TypeForm = Literal[f""] # Error: f-strings not allowed in type expression
bad7: TypeForm = ClassVar[int] # Error: ClassVar not allowed in type expression
bad8: TypeForm = Required[int] # Error: Required not allowed in type expression
bad9: TypeForm = Final[int] # Error: Final not allowed in type expression
bad10: TypeForm = Unpack[Ts] # Error: Unpack not allowed in this context
bad11: TypeForm = Optional # Error: Invalid use of Optional special form
bad12: TypeForm = T # Error if T is an out-of-scope TypeVar
bad13: TypeForm = "int + str" # Error: invalid quoted type expression
显式 TypeForm 评估
TypeForm 也可以作为一个可以接受单个参数的函数。类型检查器应验证此参数是有效的类型表达式
x1 = TypeForm(str | None)
reveal_type(v1) # Revealed type is "TypeForm[str | None]"
x2 = TypeForm("list[int]")
revealed_type(v2) # Revealed type is "TypeForm[list[int]]"
x3 = TypeForm('type(1)') # Error: invalid type expression
在运行时,TypeForm(...) 可调用对象只返回传递给它的值。
这种显式语法有两个目的。首先,它记录了开发者打算将该值用作类型形式对象的意图。其次,静态类型检查器验证所有类型表达式的规则都已遵循
x4 = type(1) # No error, evaluates to "type[int]"
x5 = TypeForm(type(1)) # Error: call not allowed in type expression
可赋值性
TypeForm 具有单个类型参数,它是协变的。这意味着如果 B 可赋值给 A,则 TypeForm[B] 可赋值给 TypeForm[A]
def get_type_form() -> TypeForm[int]: ...
t1: TypeForm[int | str] = get_type_form() # OK
t2: TypeForm[str] = get_type_form() # Error
type[T] 是 TypeForm[T] 的子类型,这意味着如果 B 可赋值给 A,则 type[B] 可赋值给 TypeForm[A]
def get_type() -> type[int]: ...
t3: TypeForm[int | str] = get_type() # OK
t4: TypeForm[str] = get_type() # Error
TypeForm 是 object 的子类型,并假定具有 object 的所有属性和方法。
向后兼容性
本 PEP 澄清了静态类型检查器在“值表达式”上下文(即类型表达式不是类型规范强制要求的上下文)中评估类型表达式的行为。在没有 TypeForm 类型注解的情况下,现有类型评估行为将持续存在,因此预计不会出现向后兼容性问题。例如,如果静态类型检查器以前将表达式 str | None 的类型评估为 UnionType,那么除非将此表达式赋值给类型注解为 TypeForm 的变量或参数,否则它将继续这样做。
如何教授此内容
类型表达式用于注解,以描述函数参数接受哪些值、函数返回哪些值或变量存储哪些值
parameter type return type
| |
v v
def plus(n1: int, n2: int) -> int:
sum: int = n1 + n2
^
|
variable type
return sum
类型表达式在运行时评估为有效的 类型形式 对象,可以像程序中的任何其他数据一样赋值给变量并进行操作
a variable a type expression
| |
v v
int_type_form: TypeForm = int | None
^
|
the type of a type form object
TypeForm[] 是 类型形式 对象的类型拼写方式,它是类型的运行时表示。
TypeForm 类似于 type,但 type 仅与 类对象 兼容,例如 int、str、list 或 MyClass。TypeForm 适用于可以使用有效类型表达式表达的任何类型形式,包括带有方括号(list[int])、联合运算符(int | None)和特殊形式(Any、LiteralString、Never 等)的类型形式。
大多数程序员不会定义自己的接受 TypeForm 参数或返回 TypeForm 值的函数。更常见的是将类型形式对象传递给知道如何解码和使用此类对象的库函数。
例如,trycast 库中的 isassignable 函数可以像 Python 的内置 isinstance 函数一样使用,以检查值是否与特定类型形状匹配。isassignable 接受 任何 类型形式对象作为输入。
- 是
from trycast import isassignable if isassignable(some_object, MyTypedDict): # OK: MyTypedDict is a TypeForm[] ...
- 否
if isinstance(some_object, MyTypedDict): # ERROR: MyTypedDict is not a type[] ...
高级示例
如果您想编写自己的运行时类型检查器或一个在运行时将类型形式对象作为值进行操作的函数,本节提供了此类函数如何使用 TypeForm 的示例。
内省类型形式对象
像 typing.get_origin 和 typing.get_args 这样的函数可以用来提取某些类型形式对象的组件。
import typing
from typing import TypeForm, cast
def strip_annotated_metadata[T](typx: TypeForm[T]) -> TypeForm[T]:
if typing.get_origin(typx) is typing.Annotated:
typx = cast(TypeForm[T], typing.get_args(typx)[0])
return typx
isinstance 和 is 也可以用来区分不同类型的类型形式对象
import types
import typing
from typing import TypeForm, cast
def split_union(typx: TypeForm) -> tuple[TypeForm, ...]:
if isinstance(typx, types.UnionType): # X | Y
return cast(tuple[TypeForm, ...], typing.get_args(typx))
if typing.get_origin(typx) is typing.Union: # Union[X, Y]
return cast(tuple[TypeForm, ...], typing.get_args(typx))
if typx in (typing.Never, typing.NoReturn,):
return ()
return (typx,)
与类型变量结合使用
TypeForm 可以通过在同一函数定义中其他地方使用的类型变量进行参数化
def as_instance[T](typx: TypeForm[T]) -> T | None:
return typx() if isinstance(typx, type) else None
与 type 结合使用
TypeForm 和 type 都可以通过在同一函数定义中使用的相同类型变量进行参数化
def as_type[T](typx: TypeForm[T]) -> type[T] | None:
return typx if isinstance(typx, type) else None
与 TypeIs 和 TypeGuard 结合使用
类型变量也可以通过 TypeIs 或 TypeGuard 返回类型使用
def isassignable[T](value: object, typx: TypeForm[T]) -> TypeIs[T]: ...
count: int | str = ...
if isassignable(count, int):
assert_type(count, int)
else:
assert_type(count, str)
接受所有 TypeForm 时的挑战
一个接受 任意 TypeForm 作为输入的函数必须支持各种可能的类型形式对象。这样的函数不容易编写。
- 每个新的 Python 版本都会引入新的特殊形式,并且可能需要对每个特殊形式进行特殊处理。
- 引用注解 [5](如
'list[str]')必须被 解析 (转换为类似list[str]的形式)。 - 解析类型表达式中引用的前向引用通常使用
eval()完成,这难以安全地使用。 - 递归类型如
IntTree = list[int | 'IntTree']难以解析。 - 用户定义的泛型类型(如 Django 的
QuerySet[User])可能会引入非标准行为,需要运行时支持。
参考实现
Pyright (版本 1.1.379) 提供了 TypeForm 的参考实现。
Mypy 贡献者也 计划实现 对 TypeForm 的支持。
运行时组件的参考实现在 typing_extensions 模块中提供。
被拒绝的想法
备选名称
曾考虑为 TypeForm 备选名称。TypeObject 和 TypeType 被认为过于通用。TypeExpression 和 TypeExpr 也曾被考虑,但这些被认为会引起混淆,因为这些对象本身不是“表达式”,而是评估类型表达式的结果。
将 type[C] 扩展以支持所有类型表达式
type 被设计用于描述类对象,即 type 类的子类。类型为 type 的值被假定可以通过构造函数调用进行实例化。扩大 type 的含义以表示任意类型形式对象将带来向后兼容性问题,并会消除一种描述仅限于 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 allowed here
除了 Annotated 之外,类型限定符不允许在类型表达式中使用。TypeForm 仅限于类型表达式,因为其可赋值性规则基于类型的可赋值性规则。询问 Final[int] 是否可赋值给 int 是没有意义的,因为前者不是有效的类型表达式。
希望对从注解表达式评估的对象进行操作的函数可以继续接受此类输入作为 object 参数。
类型形式的模式匹配
有人断言,有些函数可能希望在其签名中对类型表达式的内部进行模式匹配。
一个用例是允许函数显式枚举它支持作为输入的所有 特定 类型表达式。考虑以下可能的模式匹配语法
@overload
def checkcast(typx: TypeForm[AT=Annotated[T, *A]], value: str) -> T: ...
@overload
def checkcast(typx: TypeForm[UT=Union[*Ts]], value: str) -> Union[*Ts]: ...
@overload
def checkcast(typx: type[C], value: str) -> C: ...
# ... (more)
在实践中观察到的所有概念上接受类型形式对象的函数通常都试图支持 所有 类型的表达式,因此枚举特定的子集似乎没有价值。
此外,上述语法不够精确,无法完全描述实际典型函数的输入约束。例如,许多函数不支持带有引用子表达式的类型表达式,例如 list['Movie']。
模式匹配的第二个用例是显式匹配 Annotated 形式,以提取内部类型参数并去除任何元数据
def checkcast(
typx: TypeForm[T] | TypeForm[AT=Annotated[T, *A]],
value: object
) -> T:
然而,Annotated[T, metadata] 已经被静态类型检查器视为等同于 T。显式说明这种行为没有额外价值。上面的例子可以更简单地写成等价的形式
def checkcast(typx: TypeForm[T], value: object) -> T:
致谢
- David Foster 起草了本 PEP 的最初版本,起草了它的 mypy 实现,并将其引导通过 PEP 流程。
- Eric Traut 在整个设计过程中提供了大量反馈,起草了对原始 PEP 文本的重大更新,并起草了它的 pyright 实现。
- Jelle Zijlstra 提供了反馈,特别是对 PEP 的早期草稿,并起草了
TypeExpr特殊形式的typing_extensions实现。 - Carl Meyer 和 Mehdi Drissi 提供了宝贵的反馈,特别是在是否允许
type赋值给TypeForm的问题上。 - Cecil Curry (leycec) 从运行时类型检查器的角度提供了反馈,并在一个真实的运行时类型检查器 (beartype) 中试验了正在进行的
TypeForm特殊形式。 - Jukka Lehtosalo 对 TypeForm 的 mypy 实现提供了反馈,帮助检查算法运行更快并使用更少的内存。
- Michael H (mikeshardmind) 提出了匹配特定类型形式的语法想法。
- Paul Moore 倡导对 PEP 进行一些更改,使其对类型新手更易于理解。
- Tin Tvrtković (Tinche) 和 Salvo ‘LtWorf’ Tomaselli 多次提供了来自更广泛社区的积极反馈,支持 PEP 将有用。
脚注
版权
本文档置于公共领域或 CC0-1.0-Universal 许可证下,以更宽松者为准。
来源: https://github.com/python/peps/blob/main/peps/pep-0747.rst
最后修改: 2025-07-22 01:46:04 GMT