如何在Python类型系统中定义约束元数据格式的Annotated类型?
首先得明确一点:原生的Python类型系统(包括PEP 484及相关扩展)没办法直接定义出你想要的AnnotatedWithExamples类型。因为Annotated的设计初衷就是让元数据部分可以是任意对象,类型检查器只会关注它的第一个类型参数(也就是被注解的基础类型),对后面的元数据内容不会做任何类型校验——元数据对类型检查器来说是完全透明的。
那有没有办法在不依赖mypy插件的前提下,接近你想要的效果呢?当然有,我们可以换个思路:从创建Annotated类型的环节入手,用工厂函数来规范元数据的格式,而不是在使用extract_examples时再做检查。
举个具体的例子:
from typing import Annotated, TypeVar T = TypeVar('T') def create_annotated_with_examples(base_type: type[T], examples: tuple) -> Annotated[T, tuple]: # 运行时层面的校验:确保传入的examples是元组 if not isinstance(examples, tuple): raise ValueError("示例必须是元组类型!") return Annotated[base_type, examples] # 正确的用法:元数据是符合要求的元组 PositiveIntWithExamples = create_annotated_with_examples(int, (12, 1001)) # 错误的用法:不管是类型检查还是运行时都会报错 # Bad = create_annotated_with_examples(int, "This is bad.") # mypy会提示类型不匹配,运行时直接抛出ValueError def extract_examples(annotated_type: Annotated[T, tuple]) -> tuple: return annotated_type.__metadata__[0] # 正常调用 extract_examples(PositiveIntWithExamples) # 如果传入错误的Annotated类型,mypy虽然不会直接检查元数据,但如果这个类型不是通过工厂函数创建的,你大概率能提前发现问题
这种方式的好处是,从源头就杜绝了不符合要求的元数据被传入Annotated,既保证了运行时的正确性,也能让类型检查器在创建Annotated时就给出提示。
那回到你最开始的需求:能不能直接定义AnnotatedWithExamples作为类型注解,让extract_examples的参数被严格约束?答案还是不行——除非你用mypy插件。因为原生类型系统没有提供“检查Annotated元数据结构”的能力,而mypy插件可以自定义类型检查逻辑:拦截对extract_examples的调用,解析传入的Annotated类型,检查它的第一个元数据是不是元组,如果不是就抛出类型错误。
另外你提到的NewType也帮不上忙,它只是创建一个类型别名,本质上还是原来的类型,没办法对Annotated的元数据做任何约束。
总结一下:
- 原生类型系统无法直接定义约束元数据格式的Annotated子类型;
- 用工厂函数可以在创建Annotated时规范元数据,达到类似的效果;
- 如果必须在类型检查阶段直接校验参数的元数据结构,就得依赖mypy插件。
备注:内容来源于stack exchange,提问作者Jonathan Herrera

