跳转到内容

Python数据类

dataclass 是 Python 在 3.7 版本 中引入的一个装饰器(Decorator),它极大地简化了我们创建 主要用于存储数据的类(即数据类)的过程。使用 dataclass 可以自动为类添加很多特殊方法(magic methods),从而显著减少样板代码(boilerplate code)。

一个数据类(Data Class)是一个主要功能是 存储状态 而非实现复杂行为的类。在 Python 中,这意味着我们需要编写大量的 __init__, __repr__, __eq__ 等方法来确保这些类能够正常工作。@dataclass 装饰器就是用来自动生成这些必要方法的。

传统 Python 类 (需要大量样板代码):

class Point:
def __init__(self, x: int, y: int):
self.x = x
self.y = y
def __repr__(self):
return f"Point(x={self.x}, y={self.y})"
# 如果需要对象相等性比较,还需要手动实现 __eq__ 等方法

使用 dataclass 的等效代码 (简洁高效):

from dataclasses import dataclass
@dataclass
class Point:
x: int
y: int
# 实例化和使用方式完全相同
p1 = Point(x=10, y=20)
print(p1) # 自动生成了 __repr__ 方法
# 输出: Point(x = 10, y = 20)

2. dataclass 自动生成的“魔法”方法

Section titled “2. dataclass 自动生成的“魔法”方法”

当您使用 @dataclass 装饰一个类时,它会基于您定义的 类型注解 自动生成以下方法:

魔法方法作用对应 dataclass 参数
__init__构造函数,用于初始化字段。默认自动生成
__repr__对象的字符串表示形式,通常用于日志和调试。默认自动生成 (repr=True)
__eq__相等性比较。根据所有字段的值进行逻辑比较。默认自动生成 (eq=True)
__lt__, __le__, __gt__, __ge__大小比较(<, <=, >, >=)。默认按定义的属性字段顺序依次比较。order=True
__hash__哈希值计算。用于将对象放入集合(set)或作为字典键。frozen=Trueeq=False 时自动生成
__post_init__非自动生成,是您可以手动定义的回调函数,在 __init__ 完成后立即调用,用于额外的初始化或验证。手工编写

@dataclass 装饰器接受几个布尔参数,用于控制要生成的行为。

@dataclass(*, init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False)
  • 作用:自动生成 __init__ 构造函数。
  • 用途:如果您想手动控制初始化过程,可以将其设为 False(并不常见)。
  • 作用:自动生成 __repr__ 方法,输出清晰易读的格式(包含所有字段名和对应的值)。
  • 作用:自动生成 __eq__ 方法,允许您比较两个对象。
p1 = Point(1, 2)
p2 = Point(1, 2)
p3 = Point(3, 4)
print(p1 == p2) # True
print(p1 == p3) # False
  • 作用:如果设置为 True,则会自动生成大小比较方法(__lt__, __le__, __gt__, __ge__)。
  • 原理:比较是 按字段定义的先后顺序 逐个进行的(类似于元组的比较)。
@dataclass(order=True)
class Item:
priority: int
name: str
i1 = Item(1, "Apple")
i2 = Item(2, "Banana")
print(i1 < i2) # True (因为 1 < 2)
  • 作用:如果设置为 True,则将创建 不可变(Immutable)的数据类。
  • 效果:任何尝试在对象创建后修改字段值的行为都会抛出 FrozenInstanceError
  • 额外作用:当 frozen=True 时,Python 会自动生成 __hash__ 方法,使对象可以作为字典的键或添加到集合中。
@dataclass(frozen=True)
class Config:
timeout: int
c = Config(timeout=10)
# c.timeout = 20 # 会抛出 FrozenInstanceError 异常
my_set = {c} # 可以作为集合元素使用

对于更精细的字段控制,您需要导入 field 函数。

4.1. A. 排除字段的初始化或表示 (init, repr)

Section titled “4.1. A. 排除字段的初始化或表示 (init, repr)”

有时您不希望某些字段参与初始化或打印输出:

from dataclasses import dataclass, field
import time
@dataclass
class LogEntry:
message: str
# 自动生成,但不参与 __init__,需要在 __post_init__ 中设置
timestamp: float = field(init=False)
# 自动生成,但不参与 __repr__
internal_id: str = field(repr=False, default="")
def __post_init__(self):
# 在 __init__ 执行完毕后,手动设置 timestamp
self.timestamp = time.time()
entry = LogEntry(message="System started")
print(entry)
# 输出: LogEntry(message ='System started', internal_id ='')
# 注意:timestamp 没有出现在 __repr__ 中

如果默认值是一个 可变类型(如列表 list、字典 dict),必须 使用 default_factory 来提供一个无参数的函数(工厂函数)来创建默认值,而不能直接使用 default=[]

这是为了避免所有实例共享同一个可变对象实例的问题。

from dataclasses import dataclass, field
from typing import List
@dataclass
class User:
name: str
# 错误用法示例:如果直接写 tasks: List [str] = [],所有 User 实例将共享同一个列表实例
tasks: List[str] = field(default_factory=list) # 正确用法
user1 = User("Alice")
user2 = User("Bob")
user1.tasks.append("Read documentation")
print(user1.tasks) # ['Read documentation']
print(user2.tasks) # [] (确保了 user2 有一个新的、独立的空列表)

5. dataclass 与其他数据容器类的横向比较

Section titled “5. dataclass 与其他数据容器类的横向比较”
特性dataclasstyping.NamedTuplepydantic.BaseModel
主要用途简洁地创建数据类,减少样板代码。创建不可变的、轻量级、具有类型注解的元组。强大的数据验证、配置加载、JSON 序列化/反序列化。
可变性默认可变,支持 frozen=True 实现不可变。默认 不可变默认可变。
自动验证。仅进行简单的类型注解检查,不验证数据内容。。强大的运行时数据校验和类型强转。
默认方法__init__, __repr__, __eq__, __hash__ 等。__init__, __repr__, __eq__,以及元组的原生方法。model_dump(), model_dump_json(), model_validate() 等。

总结: dataclass 是处理简单、结构化数据的最佳选择。如果您的需求涉及复杂的数据输入校验、Web API 参数解析验证或序列化/反序列化,更推荐选用 Pydantic