如何为类的__init__函数添加类型注解,实现子类类型校验?
为Mapping子类的__init__添加类型注解的优雅方案
要解决你的问题——让继承MyClass的子类无需重复编写__init__,同时让mypy能检查必填参数——有两种实用且简洁的方案可选:
方案一:使用类装饰器自动生成带类型注解的__init__
这个方案模仿dataclass的实现思路,通过装饰器提取子类的类变量类型,自动生成符合要求的__init__方法,既避免重复代码,又能被mypy识别。
实现装饰器
from typing import Any, Mapping, TypeVar, Type, Callable, cast T = TypeVar('T', bound='MyClass') def enforce_init_fields(cls: Type[T]) -> Type[T]: # 过滤掉类中的特殊注解(如__annotations__自身) required_fields = { name: annotation for name, annotation in cls.__annotations__.items() if not name.startswith('__') } if not required_fields: return cls # 动态生成__init__方法的代码 param_defs = ', '.join([f'{name}: {annotation.__name__}' for name, annotation in required_fields.items()]) super_call_args = ', '.join([f'{name}={name}' for name in required_fields.keys()]) init_code = f''' def __init__(self, {param_defs}, **kwargs): super().__init__({super_call_args}, **kwargs) ''' # 执行代码并绑定到子类 local_namespace = {} exec(init_code, globals(), local_namespace) cls.__init__ = cast(Callable[..., None], local_namespace['__init__']) return cls
子类使用示例
from typing import Any, Mapping class MyClass(Mapping[str, Any]): def __init__(**kwargs): self._kwargs = kwargs def __getitem__(self, key: str) -> Any: return self._kwargs[key] def __getattr__(self, name: str) -> Any: try: return self.__getitem__(name) except KeyError: raise AttributeError(name) from None def __iter__(self): return iter(self._kwargs) def __len__(self): return len(self._kwargs) @enforce_init_fields class SubClass(MyClass): foo: int bar: str
此时mypy会自动检查:
SubClass(foo=3, bar="test")合法SubClass(bar="test")会报错(缺少必填参数foo)SubClass(foo="str", bar="test")会报错(foo类型不匹配)
注意:如果mypy无法识别动态生成的
__init__,可以为装饰器添加类型存根(.pyi文件),明确标注装饰器会为类添加带指定参数的__init__方法。
方案二:结合Generic与TypedDict
如果你不想使用动态代码生成,可以通过Generic和TypedDict让mypy原生支持必填参数检查,只需为每个子类定义对应的字段类型字典:
实现代码
from typing import Any, Mapping, TypedDict, TypeVar, Generic # 定义TypedDict的类型变量 T = TypeVar('T', bound=TypedDict) class MyClass(Mapping[str, Any], Generic[T]): def __init__(self, **kwargs: T): self._kwargs = kwargs # 其余方法(__getitem__、__getattr__等)保持不变 # 定义子类的必填字段类型 class SubClassFields(TypedDict): foo: int bar: str class SubClass(MyClass[SubClassFields]): pass
这个方案完全依赖mypy的原生类型检查,无需额外装饰器代码,缺点是每个子类需要额外定义对应的TypedDict类,但比重复编写__init__要简洁得多。
对你尝试方案的补充说明
- PEP 692:目前mypy不支持通过动态修改
__annotations__来识别类型注解,所以该方案暂时无法生效,需等待mypy实现相关支持。 - 动态生成__init__:虽然用
exec/eval看起来有点hacky,但这正是dataclass的底层实现思路,封装成装饰器后完全可以优雅使用。 - 设置__signature__:mypy直接解析代码AST,不依赖
inspect模块获取签名,所以修改__signature__无法让mypy识别类型注解。
内容的提问来源于stack exchange,提问作者Mr Tsjolder from codidact
相关产品推荐
相关产品推荐

