You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为类的__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__要简洁得多。

对你尝试方案的补充说明

  1. PEP 692:目前mypy不支持通过动态修改__annotations__来识别类型注解,所以该方案暂时无法生效,需等待mypy实现相关支持。
  2. 动态生成__init__:虽然用exec/eval看起来有点hacky,但这正是dataclass的底层实现思路,封装成装饰器后完全可以优雅使用。
  3. 设置__signature__:mypy直接解析代码AST,不依赖inspect模块获取签名,所以修改__signature__无法让mypy识别类型注解。

内容的提问来源于stack exchange,提问作者Mr Tsjolder from codidact

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 06:40:47