编写Mypy插件:将自定义Possibly类型替换为NotRequired[Optional[T]]
实现
Possibly[T]等价于NotRequired[Optional[T]]的Mypy插件方案 因为NotRequired是TypedDict专属的元数据标记(不属于常规类型系统),无法通过纯Python类型别名实现,必须通过Mypy插件在TypedDict的分析阶段介入处理。以下是具体实现方向和代码:
核心思路
NotRequired的作用是将TypedDict的字段标记为可选,同时字段的实际类型是Optional[T]。所以插件需要做两件事:
- 识别TypedDict中使用
Possibly[T]的字段 - 将该字段类型替换为
Optional[T],并把字段加入TypedDict的可选键集合
插件实现代码
1. 定义Possibly泛型占位类
# possibly.__init__.py from typing import Generic, TypeVar T = TypeVar("T") class Possibly(Generic[T]): pass
2. 编写Mypy插件
# possibly.plugin from mypy.plugin import Plugin, TypedDictContext from mypy.nodes import TypedDictType from mypy.types import Type, UnionType, NoneType, Instance class PossiblyPlugin(Plugin): def get_typed_dict_hook(self) -> callable | None: # 拦截TypedDict的分析过程 return self.process_typed_dict def process_typed_dict(self, ctx: TypedDictContext) -> TypedDictType | None: td_type = ctx.typed_dict_type new_fields = {} new_optional_keys = set(td_type.optional_keys) # 遍历TypedDict的所有字段 for field_name, field_type in td_type.items.items(): # 判断字段类型是否为Possibly[X] if (isinstance(field_type, Instance) and field_type.type.fullname == "possibly.Possibly" and len(field_type.args) == 1): # 提取泛型参数X,构造Optional[X] inner_type = field_type.args[0] optional_type = UnionType([inner_type, NoneType()]) new_fields[field_name] = optional_type # 将字段标记为可选,对应NotRequired的效果 new_optional_keys.add(field_name) else: new_fields[field_name] = field_type # 返回修改后的TypedDict类型 return TypedDictType( items=new_fields, optional_keys=new_optional_keys, total=td_type.total, fallback=td_type.fallback, metadata=td_type.metadata, ) def get_type_analyze_hook(self, fullname: str) -> callable | None: # 限制Possibly只能在TypedDict中使用 if fullname == "possibly.Possibly": return self.validate_possibly_usage def validate_possibly_usage(self, ctx) -> Type | None: # 非TypedDict场景下使用Possibly则报错 ctx.api.msg.error( "`Possibly[T]`仅能作为TypedDict的字段类型使用", ctx.context ) # 返回object作为占位类型,避免后续类型错误 return ctx.api.named_type("builtins.object") def plugin(version: str) -> type[Plugin]: return PossiblyPlugin
插件配置与使用
- 在
mypy.ini中启用插件:
[mypy] plugins = possibly.plugin
- 在代码中使用
Possibly:
from typing import TypedDict from possibly import Possibly class User(TypedDict): id: int name: Possibly[str] # 等价于NotRequired[Optional[str]]
关键说明
get_typed_dict_hook是Mypy提供的专门处理TypedDict的钩子,能在TypedDict定义被分析时介入修改- 替换字段类型为
Optional[T](即Union[T, None]),同时将字段加入可选键集合,完美模拟NotRequired[Optional[T]]的效果 get_type_analyze_hook用于限制Possibly的使用场景,避免在非TypedDict环境下误用
内容的提问来源于stack exchange,提问作者Jonathan Herrera
相关产品推荐
相关产品推荐

