@dataclass 自动生成__init__、__repr__、__eq__,但要求字段必须有类型注解,禁用可变默认值,frozen=True启用只读和__slots__,asdict/astuple不递归处理嵌套dataclass。

直接用 @dataclass 装饰器,不用写 __init__、__repr__、__eq__ —— 这些都会自动生成,但得注意字段顺序、可变默认值和冻结行为这些坑。
字段必须有类型注解,否则不会被识别为 dataclass 字段
Python 3.7+ 的 @dataclass 依赖类型提示来推断字段。没有类型注解的变量(哪怕写了 field())会被忽略,不参与初始化或序列化。
常见错误:写成这样,name 和 age 不会进 __init__:
from dataclasses import dataclass <p>@dataclass class Person: name # ❌ 没类型,不算字段 age # ❌ 同上 city: str = "Beijing"
正确写法:
立即学习“Python免费学习笔记(深入)”;
- 所有字段必须带类型注解,如
name: str、scores: list[float] - 可选字段要用
Optional[]并配默认值:nickname: Optional[str] = None - 想跳过某些变量(比如运行时缓存),加
init=False:cache: dict = field(init=False, default_factory=dict)
别用可变对象(list/dict)当默认值,用 default_factory
直接写 items: list = [] 会触发经典“可变默认参数”陷阱:所有实例共享同一个 list。
错误示例:
@dataclass
class BadCart:
items: list = [] # ❌ 危险!
<p>c1 = BadCart()
c2 = BadCart()
c1.items.append("apple")
print(c2.items) # 输出 ['apple'] —— 不是预期结果
正确做法:
- 用
field(default_factory=list)或field(default_factory=lambda: [1, 2]) - 内置类型优先用简写:
items: list = field(default_factory=list) - 自定义类也一样:
config: dict = field(default_factory=dict)
需要不可变实例?设 frozen=True,但注意性能和继承限制
frozen=True 会让实例变成只读(类似 namedtuple),赋值会抛 dataclasses.FrozenInstanceError。
但要注意:
- 字段仍可被
object.__setattr__绕过(调试时有用,但不是设计意图) - 启用
frozen=True后,__slots__自动开启,内存更省,但类不能有__dict__ - 父类设了
frozen=True,子类也必须设,否则报错;反过来则允许 - 如果字段本身是可变对象(如
list),frozen只锁引用,不锁内容 —— 你仍能调.append()
asdict() 和 astuple() 不递归处理嵌套 dataclass,要小心序列化
这两个函数默认只展开一层,嵌套的 @dataclass 实例不会自动转成 dict/tuple,而是保持原对象。
例如:
@dataclass
class Address:
street: str
<p>@dataclass
class User:
name: str
addr: Address</p><p>u = User("Alice", Address("Main St"))
print(asdict(u)) # {'name': 'Alice', 'addr': Address(street='Main St')} ❌
解决方式:
- 手动递归:传
dict_factory参数,或自己写遍历逻辑 - 用第三方库如
dataclasses-json或pydantic(后者更重,但开箱支持嵌套) - 简单场景下,给嵌套类也加
asdict方法并组合调用
真正用起来,@dataclass 省事是真省,但类型注解、默认值、冻结、嵌套这四块最容易漏看文档就掉坑里。


















