@dataclass 依据字段注解自动生成 __init__/__repr__/__eq__,用几行声明替代成片的样板代码。

基本用法

from dataclasses import dataclass
 
@dataclass
class Point:
    x: float
    y: float = 0.0

等价于手写:

class Point:
    def __init__(self, x: float, y: float = 0.0):
        self.x = x
        self.y = y
    def __repr__(self): ...   # Point(x=..., y=...)
    def __eq__(self, other): ...   # 逐字段比较

字段顺序即 __init__ 参数顺序,无默认值的字段必须在前。字段本质是 类属性与实例属性 的自动化管理——类体上只留注解与默认值,实例属性在生成的 __init__ 里赋上。

可变默认值:default_factory

默认参数在定义时求值一次并被共享,直接写 items: list = [] 会让所有实例共享同一列表。dataclass 干脆禁止这种写法,必须用工厂函数:

from dataclasses import dataclass, field
 
@dataclass
class Trainer:
    lr: float = 1e-3
    history: list[float] = field(default_factory=list)  # 每实例新列表
 
    def log(self, loss: float):
        self.history.append(loss)

field() 另有 init=False(不进构造参数)、repr=False(不进 repr)等开关。

frozen / slots / kw_only

@dataclass(frozen=True, slots=True, kw_only=True)
class Config:
    name: str
    version: str = "1.0"
  • frozen=True:实例不可变,赋值抛 FrozenInstanceError;同时生成 __hash__,可作 dict 键、可入 set
  • slots=True(3.10+):生成 __slots__,省去实例 __dict__,大批量对象内存占用显著下降(原理见 属性访问与查找)
  • kw_only=True(3.10+):参数强制关键字传递,未来加字段不破坏位置参数兼容性

post_init 钩子

生成的 __init__ 末尾自动调用,做派生字段与校验:

@dataclass
class Rect:
    w: float
    h: float
    area: float = field(init=False)
 
    def __post_init__(self):
        if self.w < 0 or self.h < 0:
            raise ValueError("边长不能为负")
        self.area = self.w * self.h

与 NamedTuple / attrs 取舍

方案特点
@dataclass标准库;hooks、frozen/slots/kw_only 参数化最全
NamedTuple不可变、tuple 子类、可解包、更省内存
attrs第三方;validator/converter 更强,dataclass 的前身

不可变的值对象优先 NamedTuple;需要 __post_init__ 与各种开关组合用 dataclass;重度校验上 attrs 或 Pydantic。

生成的 __init__/__repr__/__eq__ 全是 特殊方法,任何一个都可手动覆盖。字段类型注解遵循 类型注解 typing,且是 dataclass 识别字段的唯一依据——没有注解的类属性不会成为字段。