Python 的 dataclass:少写样板代码,但默认值与可变字段要先讲清
Python 的 dataclass:少写样板代码,但默认值与可变字段要先讲清
写 Python 类时,最常见的样板就是 __init__:十几个字段,就要写十几行赋值,还容易漏写 __repr__ 和 __eq__。dataclass 正是为了解决这个问题:用装饰器声明字段,剩下的由它生成。
from dataclasses import dataclass
@dataclass
class User:
name: str
age: int = 18
active: bool = True
这样就自动得到了 __init__、__repr__ 和按字段比较的 __eq__。打印对象时不再是晦涩的内存地址,而是 User(name='Li', age=18, active=True),调试和写日志都直观得多。
但第一个坑就在默认值上:可变对象不能直接当默认值。下面这段代码会直接报错:
@dataclass
class Team:
members: list = [] # TypeError:可变默认值不允许
原因是默认值只在类定义时求值一次,所有实例会共享同一个列表,一个实例改了,其他实例也跟着变。正确做法是用 field 工厂函数:
from dataclasses import dataclass, field
@dataclass
class Team:
members: list = field(default_factory=list)
每个实例调用 list() 新建一个空列表,互不干扰。字典、集合同理,都要用 default_factory。
第二个要点是字段顺序:没有默认值的字段必须排在有默认值的字段前面,否则 __init__ 的参数顺序会产生矛盾,解释器会直接报错。习惯上把必填字段放前面、可选字段放后面,必要时用 field(default=...) 显式标注。
第三个要点是 frozen 与可变性。默认的 dataclass 实例属性可以修改;如果希望它像值对象一样不可变,可以加 @dataclass(frozen=True)。但要注意,frozen 只冻住属性赋值,不冻住属性内部的可变对象:列表字段里的元素仍然可以改,真正的不可变还得靠元组或自己控制。
还有一个实用技巧:__post_init__。有些校验或派生字段不适合放在声明里,可以在这里做:
@dataclass
class Order:
price: float
count: int
total: float = 0.0
def __post_init__(self):
if self.count <= 0:
raise ValueError("count 必须大于 0")
self.total = self.price * self.count
这样构造时就能拦住非法数据,而不是等到后续计算才暴露问题。
总结一下:dataclass 适合替代以数据为主的简单类,省掉大量样板;但要记住三条:可变默认值用 default_factory,必填字段排在可选字段之前,需要不可变时用 frozen 并注意它只冻住第一层。把这三处讲清,dataclass 才会真正省心,而不是在运行时埋下共享状态的暗坑。
- 点赞
- 收藏
- 关注作者
评论(0)