@dataclass 默认自动生成 __init__、__repr__、__eq__ 方法,字段需类型注解,带默认值字段须在无默认值之后;可用 field() 控制 init/repr 行为,继承时注意字段顺序与 __post_init__ 手动调用父类。

dataclass 自动生成 __init__ 和 __repr__ 是默认行为
只要加了 @dataclass 装饰器,Python 就自动给你生成 __init__、__repr__、__eq__ 这几个方法,不用手写。你定义的字段会按顺序变成 __init__ 的参数,值直接赋给实例属性;__repr__ 则按字段名=值的格式打印,比如 Person(name='Alice', age=30)。
常见错误是以为要手动实现这些方法,或者误加 __init__ 导致装饰器失效——一旦自定义了 __init__,@dataclass 就不再生成它。
- 字段必须用类型注解(如
name: str),否则不会被识别为 dataclass 字段 - 带默认值的字段必须放在无默认值字段之后,否则报
TypeError: non-default argument follows default argument - 如果只想开
__repr__不想要__eq__,可以传eq=False,但__init__和__repr__默认总是一起启用
想控制字段是否参与 __init__ 或 __repr__?用 field()
有些字段你不希望用户在初始化时传入(比如缓存、运行时计算值),或不想出现在 __repr__ 里(比如敏感信息、大对象),就得用 field() 显式配置。
典型场景:API 响应类中,id 由服务端返回,客户端不传;_cache 是内部用的,不该进 repr。
立即学习“Python免费学习笔记(深入)”;
-
init=False:该字段不进__init__参数列表,但仍是实例属性(需在__post_init__或其他地方赋值) -
repr=False:该字段不出现在__repr__输出中,但依然可访问 -
default或default_factory必须和init=True配合使用,否则会报错
示例:score: float = field(init=False, repr=False) —— 这个字段既不能初始化传入,也不会出现在 repr 字符串里。
继承 dataclass 时字段顺序和 __post_init__ 容易出错
子类 dataclass 继承父类 dataclass 时,所有字段(包括父类的)会合并进同一个 __init__,且要求父类字段在前、子类字段在后。如果父类字段没默认值而子类字段有,默认值顺序一乱就报错。
更隐蔽的问题是:父类和子类都定义了 __post_init__,但子类不会自动调用父类的——得手动写 super().__post_init__(),否则父类的初始化逻辑就丢了。
- 避免混合使用带/不带默认值的字段跨层级定义;统一用
field(default=...)显式声明更安全 -
__post_init__不是构造函数,它在__init__之后运行,适合做校验、转换、补全等操作 - 如果父类不是 dataclass,子类加了
@dataclass,那父类字段完全不参与 dataclass 行为,得自己处理
dataclass 在序列化(如 JSON)和冻结对象时要注意兼容性
@dataclass 本身不提供 to_dict 或 to_json 方法,也不阻止修改属性。想转成字典常配合 asdict(),但它默认递归处理嵌套 dataclass,遇到非 dataclass 对象(比如 datetime、numpy array)就崩。
冻结(frozen=True)看似安全,但只是给属性加了只读保护,底层 __dict__ 还能被绕过(比如 object.__setattr__(obj, 'x', 1)),别当真不可变用。
-
asdict()和astuple()不处理函数、lambda、模块等不可序列化对象,提前过滤或自定义转换器 -
frozen=True会让类变慢(每次赋值都要检查),且和__slots__不兼容(会报ValueError) - 需要真正不可变语义?考虑用
NamedTuple或第三方库如pydantic,dataclass 的 frozen 只是轻量防护
最常被忽略的是:dataclass 字段类型注解只是提示,Python 运行时不强制;想靠它做校验或序列化,得额外加逻辑,不能默认它“管用”。










