如何为自定义非dataclass类实现__init__注解自动生成?
为非dataclass类实现带类型注解的自动__init__方法
Python的@dataclass装饰器可以自动为类生成带类型注解的__init__方法,但如果要给自定义非dataclass类实现相同功能,同时兼容mypy,完全可以用纯Python代码实现——核心思路是通过类的__annotations__属性提取类型信息,动态生成符合要求的__init__方法。
实现方案:自定义装饰器
我们可以写一个装饰器,在类定义完成后动态生成并替换类的__init__方法,同时处理类型注解和默认值,确保mypy能正确识别参数类型。
装饰器代码
from typing import TypeVar T = TypeVar('T') def auto_init(cls: type[T]) -> type[T]: # 提取类的类型注解 field_annotations = cls.__annotations__.copy() # 提取带注解且有默认值的类属性 class_defaults = {k: v for k, v in cls.__dict__.items() if not k.startswith('__') and k in field_annotations} # 构建__init__的参数列表 init_params = [] for field_name, field_type in field_annotations.items(): # 兼容复杂类型(如list[int])的字符串表示 type_str = repr(field_type) if not hasattr(field_type, '__name__') else field_type.__name__ if field_name in class_defaults: # 带默认值的可选参数 init_params.append(f"{field_name}: {type_str} = {repr(class_defaults[field_name])}") else: # 必填参数 init_params.append(f"{field_name}: {type_str}") # 生成__init__函数代码 init_code = f""" def __init__(self, {', '.join(init_params)}): # 将参数赋值给实例属性 for key, value in locals().items(): if key != 'self': setattr(self, key, value) """ # 执行代码生成函数并替换类的__init__ namespace = {} exec(init_code, namespace) cls.__init__ = namespace['__init__'] # 为__init__添加注解,确保mypy识别类型 cls.__init__.__annotations__ = field_annotations return cls
使用示例
@auto_init class A: number: int string: str default_not_hinted_arg = None # 不带注解,不会加入__init__参数 # 实例化测试 a = A(number=456, string="hello") print(a.number) # 输出: 456 print(a.string) # 输出: hello print(a.default_not_hinted_arg) # 输出: None # mypy会正确识别__init__参数类型,传入非int类型的number会触发类型检查报错
关键细节说明
- 类型注解处理:通过
cls.__annotations__获取类中带类型注解的字段,生成对应参数的类型标注,兼容基础类型和泛型复杂类型。 - 默认值处理:如果带注解的字段在类中有初始赋值,会自动将其设为
__init__的可选参数,和@dataclass的行为一致。 - mypy兼容性:手动为生成的
__init__方法设置__annotations__属性,解决直接使用__annotations__传递参数时的兼容性问题。 - 非注解属性:类中不带类型注解的属性不会被加入
__init__的参数列表,保持和@dataclass的行为一致,这些属性会沿用类级别的默认值。
内容的提问来源于stack exchange,提问作者Some Guy
相关产品推荐
相关产品推荐

