PEP 746 – 类型检查带注释的元数据
- 作者:
- Adrian Garcia Badaracco <adrian at adriangb.com>
- 赞助者:
- Jelle Zijlstra <jelle.zijlstra at gmail.com>
- 讨论列表:
- Discourse 帖子
- 状态:
- 草稿
- 类型:
- 标准跟踪
- 主题:
- 类型提示
- 创建:
- 2024年5月20日
- Python 版本:
- 3.14
- 历史记录:
- 2024年5月20日
摘要
本 PEP 提出了一种使用typing.Annotated
类型进行类型检查元数据的机制。实现新__supports_type__
协议的元数据对象将由静态类型检查器进行类型检查,以确保元数据对于给定类型有效。
动机
PEP 593 引入了Annotated
作为一种将运行时元数据附加到类型的方法。通常,元数据不适用于静态类型检查器,但即使如此,能够检查元数据对于给定类型是否有意义通常也很有用。
以PEP 593中的第一个示例为例,它使用Annotated
将序列化信息附加到字段
class Student(struct2.Packed):
name: Annotated[str, struct2.ctype("<10s")]
这里,struct2.ctype("<10s")
元数据旨在由序列化库用于序列化字段。此类库只能序列化一部分类型:例如,编写Annotated[list[str], struct2.ctype("<10s")]
是没有意义的。然而,类型系统无法强制执行此操作。元数据被类型检查器完全忽略。
此用例出现在pydantic和msgspec等库中,这些库使用Annotated
将验证和转换信息附加到字段,或者fastapi,它使用Annotated
将参数标记为从标头、查询字符串或依赖项注入中提取。
规范
本 PEP 引入了一种协议,静态和运行时类型检查器可以使用该协议来验证Annotated
元数据与给定类型之间的一致性。实现此协议的对象具有一个名为__supports_type__
的属性,该属性指定元数据对于给定类型是否有效
class Int64:
__supports_type__: int
该属性也可以标记为ClassVar
以避免与数据类交互
from dataclasses import dataclass
from typing import ClassVar
@dataclass
class Gt:
value: int
__supports_type__: ClassVar[int]
当静态类型检查器遇到Annotated[T, M1, M2, ...]
形式的类型表达式时,它应强制执行以下条件之一对于M1, M2, ...
中的每个元数据元素:
- 元数据元素计算结果为一个没有
__supports_type__
属性的对象;或者 - 元数据元素计算结果为一个具有
__supports_type__
属性的对象M
;并且T
可分配给M.__supports_type__
的类型。
为了支持泛型Gt
元数据,可以编写
from typing import Protocol
class SupportsGt[T](Protocol):
def __gt__(self, __other: T) -> bool:
...
class Gt[T]:
__supports_type__: ClassVar[SupportsGt[T]]
def __init__(self, value: T) -> None:
self.value = value
x1: Annotated[int, Gt(0)] = 1 # OK
x2: Annotated[str, Gt(0)] = 0 # type checker error: str is not assignable to SupportsGt[int]
x3: Annotated[int, Gt(1)] = 0 # OK for static type checkers; runtime type checkers may flag this
向后兼容性
不实现该协议的元数据将被认为对所有类型都有效,因此不会对现有代码引入重大更改。新的检查仅适用于明确实现本 PEP 指定的协议的元数据对象。
安全影响
无。
如何教授
此协议主要用于提供Annotated
元数据的库;这些库的最终用户不太可能需要自己实现该协议。应在typing.Annotated
的文档和类型规范中提及该协议。
参考实现
目前还没有。
被拒绝的想法
引入类型变量而不是泛型类
我们考虑过使用一个特殊的类型变量AnnotatedT = TypeVar("AnnotatedT")
来表示Annotated
中内部类型的类型T
;元数据将根据此类型变量进行类型检查。但是,这需要使用旧的类型变量语法(在PEP 695之前),现在已成为不鼓励使用的功能。此外,这将以一种不适合类型系统其余部分的非常规方式使用类型变量。
在typing.py
中引入一个新类型,所有元数据对象都应成为其子类
PEP 的先前版本建议添加一个新的泛型基类TypedMetadata[U]
,元数据对象将成为其子类。如果元数据对象是TypedMetadata[U]
的子类,则类型检查器将检查注释的基类型是否可分配给U
。但是,此机制与语言的其余部分集成得不好;Python 通常不使用标记基类。此外,它提供的灵活性不如当前的提议:它不允许重载,并且它要求元数据对象添加一个新的基类,这可能会使它们的运行时实现更加复杂。
使用方法而不是属性来表示__supports_type__
我们考虑过对协议使用方法而不是属性,以便可以在运行时使用此方法来检查元数据的有效性并支持重载或返回布尔文字。但是,使用方法会增加实现的样板代码,并且运行时用例或涉及重载和返回布尔文字的更复杂场景的价值尚不清楚。
致谢
感谢 Eric Traut 建议使用协议并从 Pyright 中实现临时支持。感谢 Jelle Zijlstra 赞助本 PEP。
版权
本文档已进入公有领域。
来源:https://github.com/python/peps/blob/main/peps/pep-0746.rst