订单是一条小记录:编号、明细、总价、是否付款。手写 __init__、__repr__、__eq__ 就是整整一页样板。@dataclass 免费生成了什么?
dataclass 是带有自动生成方法的普通类,初始化、显示和相等比较都不必手写。它适合点、用户、配置值这类小型记录。字段通过注解声明,默认值则像普通赋值一样写在后面。 你还会用 default_factory、在 __post_init__ 中校验,并用 replace 更新冻结记录。
用注解声明字段
@dataclass 装饰器读取带注解的类属性,并自动生成 __init__。创建 Point(2, 3) 会按顺序给 x 和 y 赋值。自动生成的 __repr__ 让对象在控制台里更易读。
from dataclasses import dataclass
@dataclass
class Point:
x: int
y: int
point = Point(2, 3)
print(point)一个小型记录
添加默认值
带默认值的字段可以在构造时省略。没有默认值的字段必须放在类体前面。repr 会同时显示字段名和当前值。
from dataclasses import dataclass
@dataclass
class User:
name: str
active: bool = True
print(User("Ada"))字段列表中的默认值
比较与转换
dataclass 会根据字段生成 __eq__,所以数据相同的两个对象相等。asdict 返回普通字典,便于 JSON 或测试断言。frozen=True 会禁止之后再给属性赋值。
from dataclasses import dataclass, asdict
@dataclass(frozen=True)
class Item:
name: str
price: float
item = Item("tea", 3.5)
print(asdict(item))
print(Item("tea", 3.5) == item)相等比较与普通映射
可变默认值需要工厂
dataclass 里的列表默认值会被所有实例共享,所以 Python 会直接拒绝。field(default_factory=list) 会为每个对象创建新列表。字典、集合和其他可变值同理。
from dataclasses import dataclass, field
@dataclass
class Order:
drink: str
tags: list[str] = field(default_factory=list)
first = Order("latte")
second = Order("tea")
first.tags.append("gift")
print(first.tags)
print(second.tags)
print(first.tags is second.tags)每个实例都有自己的新列表
__post_init__ 校验并派生
__post_init__ 在自动生成的 __init__ 之后运行。可以用它拒绝不可能的值,并计算必须保持一致的字段。它应该校验业务规则,而不是试图强制执行 Python 根本没检查过的类型提示。
from dataclasses import dataclass
@dataclass
class Order:
price: float
quantity: int
total: float = 0.0
def __post_init__(self):
if self.price < 0 or self.quantity <= 0:
raise ValueError("invalid order")
self.total = round(self.price * self.quantity, 2)
for values in [(4.5, 2), (-1, 1)]:
try:
print(Order(*values))
except ValueError as error:
print(type(error).__name__, error)先校验,再派生总价
用 replace 更新冻结记录
frozen=True 禁止属性赋值,让记录可以安全共享。replace() 返回一个新实例,只改动指定字段,原对象保持不变。这个模式适合撤销、历史记录和配置快照。
from dataclasses import dataclass, replace
@dataclass(frozen=True)
class Order:
drink: str
price: float
original = Order("latte", 4.5)
updated = replace(original, price=5.0)
print(original)
print(updated)
print(original == updated)复制后修改,而不是原地改
本课要点
- @dataclass 会生成 __init__、__repr__ 和 __eq__。
- 字段是带注解的类属性。
- 带默认值的字段必须放在没有默认值的字段之后。
- asdict 会把 dataclass 实例转换成字典。
- frozen=True 会让实例不可变。
- 可变字段用 default_factory;__post_init__ 负责校验;冻结记录用 replace 更新。
常见错误
- 把没有默认值的字段放在有默认值的字段之后。
- 以为 dataclass 字段会在运行时校验类型。
- 试图给 frozen 实例赋值。
- 写 tags: list[str] = [],让所有实例共享同一个列表。
创建一个带 name 和 price 的 frozen Item dataclass,再打印 asdict(Item('tea', 3.5))。
动手试试
改改变量、先预测输出,再运行代码。第一次点击“运行代码”时会下载 Python 运行时,可能需要几秒钟。
练习本课
12 道题,作答后立即看到解释。