PEP 746 – 类型检查 Annotated 元数据
- 作者:
- Adrian Garcia Badaracco <adrian at adriangb.com>
- 发起人:
- Jelle Zijlstra <jelle.zijlstra at gmail.com>
- 讨论至:
- Discourse 帖子
- 状态:
- 草案
- 类型:
- 标准跟踪
- 主题:
- 类型标注
- 创建日期:
- 2024 年 5 月 20 日
- Python 版本:
- 3.15
- 发布历史:
- 2024 年 5 月 20 日
摘要
本 PEP 提出了一种类型检查元数据的机制,该机制使用 typing.Annotated 类型。实现新 __supports_annotated_base__ 协议的元数据对象将由静态类型检查器进行类型检查,以确保元数据对于给定类型有效。
动机
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_annotated_base__ 的属性,该属性指定元数据对于给定类型是否有效
class Int64:
__supports_annotated_base__: int
该属性也可以标记为 ClassVar,以避免与数据类交互
from dataclasses import dataclass
from typing import ClassVar
@dataclass
class Gt:
value: int
__supports_annotated_base__: ClassVar[int]
当静态类型检查器遇到形如 Annotated[T, M1, M2, ...] 的类型表达式时,它应该强制对于 M1, M2, ... 中的每个元数据元素,以下之一为真
- 元数据元素求值为不具有
__supports_annotated_base__属性的对象;或 - 元数据元素求值为具有
__supports_annotated_base__属性的对象M;并且T可以赋值给M.__supports_annotated_base__的类型。
为了支持泛型 Gt 元数据,可以编写
from typing import Protocol
class SupportsGt[T](Protocol):
def __gt__(self, __other: T) -> bool:
...
class Gt[T]:
__supports_annotated_base__: 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_annotated_base__ 使用方法而不是属性
我们考虑过使用方法而不是属性来表示协议,以便该方法可以在运行时用于检查元数据的有效性并支持重载或返回布尔文字。然而,使用方法会增加实现的样板,并且运行时用例或涉及重载和返回布尔文字的更复杂场景的价值尚不清楚。
致谢
我们感谢 Eric Traut 提出了使用协议并在 Pyright 中实现临时支持的建议。感谢 Jelle Zijlstra 赞助本 PEP。
版权
本文档已置于公共领域。
来源:https://github.com/python/peps/blob/main/peps/pep-0746.rst