验证@dataclass自动生成的__init__/__repr__/__eq__三个方法,以及它对可变默认值这个经典陷阱的主动拦截。
一个普通类要手写__init__/__repr__/__eq__,dataclass号称只要声明字段。
代码@dataclass class Point: x: int y: int label: str = "origin" p1 = Point(1, 2)
只写了字段声明,三个方法都能用。
结果p1 = Point(1, 2) 自动生成的__repr__: Point(x=1, y=2, label='origin') p2 = Point(1, 2) p1 == p2 (两个不同实例,字段相同): True p1 is p2: False(不是同一个对象,但__eq__按字段比较) 自动生成的__init__签名: (self, x: int, y: int, label: str = 'origin') -> None
验证了:只写了字段类型声明,@dataclass 就生成了一个完整能用的 __init__(连类型注解都保留在签名里)、一个好读的 __repr__、以及一个按字段值逐个比较的 __eq__——p1 == p2 为True但 p1 is p2 为False,正说明 __eq__ 是"比内容",不是普通类默认继承自object的那种"比地址"的相等判断。
本系列之前验证过def f(x=[])会导致调用间数据累积——dataclass怎么处理同样的写法。
代码与结果@dataclass class Bad: items: list = [] -- 模仿def f(x=[])的写法 报错: ValueError: mutable defaultfor field items is not allowed: use default_factory
结果是直接报错,而不是像普通函数那样悄悄埋下一个bug——dataclass 在生成 __init__ 的时候,会主动检查默认值是不是list/dict/set这类常见的可变类型,一旦发现就拒绝生成这个类,把问题堵在类定义阶段,而不是等到运行时才在某次调用中暴露出诡异的历史数据。
的正确写法@dataclass class Good: items: list = field(default_factory=list) g1 = Good(); g2 = Good() g1.items.append(1) 结果: g1.items=[1], g2.items=[](互不影响)
dataclass 用 field(default_factory=...) 提供了标准解法:不是直接给一个共享的默认对象,而是给一个"每次实例化时调用一次"的工厂函数,保证每个实例拿到独立的新对象——跟普通函数里用 if x is None: x = [] 是同一个思路,只是dataclass帮你把它做成了显式的、有报错保护的标准写法。
每一步都是本文上面用Python 3.14跑出来的原始结果。
@dataclass / field(default_factory=...):验证自动生成的方法与陷阱防护