环企首页
Python 之 dataclasses 模块完全指南
dataclasses 是 Python 3.7+ 引入的标准库模块,用于简化数据类的定义。它通过自动生成 __init__、__repr__、__eq__ 等特殊方法,大幅减少编写简单数据类时的样板代码。
一、基本概念
传统方式 vs dataclass
# ❌ 传统方式:样板代码太多
class Person:
def __init__(self, name: str, age: int):
self.name = name
self.age = age
def __repr__(self):
return f"Person(name={self.name!r}, age={self.age!r})"
def __eq__(self, other):
if not isinstance(other, Person):
return False
return (self.name, self.age) == (other.name, other.age)
# ✅ dataclass 方式:简洁明了
from dataclasses import dataclass
@dataclass
class Person:
name: str
age: int
二、核心装饰器参数
@dataclass 装饰器接受以下参数:
| 参数 | 默认值 | 说明 |
|---|---|---|
init |
True |
是否生成 __init__ 方法 |
repr |
True |
是否生成 __repr__ 方法 |
eq |
True |
是否生成 __eq__ 方法 |
order |
False |
是否生成比较方法(<, <=, >, >=) |
unsafe_hash |
False |
是否生成 __hash__ 方法 |
frozen |
False |
是否创建不可变对象 |
1. order=True:启用排序
@dataclass(order=True)
class Product:
price: float
name: str = ""
p1 = Product(100, "A")
p2 = Product(200, "B")
print(p1 < p2) # True(按 price 比较)
print(p1 <= p2) # True
print(p1 > p2) # False
# 自定义排序字段
@dataclass(order=True)
class Student:
score: int
name: str
# 可选:指定排序依据
def __post_init__(self):
self._sort_key = (self.score, self.name)
2. frozen=True:不可变对象
@dataclass(frozen=True)
class Point:
x: int
y: int
p = Point(1, 2)
# p.x = 10 # ❌ FrozenInstanceError: cannot assign to field 'x'
3. unsafe_hash=True:使实例可哈希
@dataclass(unsafe_hash=True)
class HashablePoint:
x: int
y: int
point_set = {HashablePoint(1, 2), HashablePoint(3, 4)} # 可用作字典键
三、字段定义与 field() 函数
基础字段定义
from dataclasses import dataclass, field
from typing import List, Optional
@dataclass
class User:
# 必填字段
username: str
# 有默认值的字段(必须放在必填字段之后)
age: int = 18
email: str = ""
# 使用 field() 定义字段
tags: List[str] = field(default_factory=list) # 避免可变默认值
metadata: dict = field(default_factory=dict)
temp_data: Optional[str] = field(default=None, init=False) # 不参与初始化
field() 函数完整参数
| 参数 | 说明 |
|---|---|
default |
字段默认值 |
default_factory |
提供默认值的无参函数(用于可变对象) |
init |
是否作为 __init__ 参数(默认 True) |
repr |
是否包含在 __repr__ 中(默认 True) |
compare |
是否参与比较(默认 True) |
hash |
是否参与哈希计算(默认 None,使用 compare 值) |
metadata |
附加信息(不被 dataclass 处理) |
高级字段示例
@dataclass
class AdvancedExample:
# 1. 隐藏敏感信息
password: str = field(repr=False) # __repr__ 中不显示
# 2. 不参与比较
cache: dict = field(default_factory=dict, compare=False)
# 3. 临时计算字段(不参与初始化)
created_at: float = field(init=False, default_factory=time.time)
# 4. 带元数据的字段
value: int = field(metadata={"unit": "meters", "min": 0})
四、特殊方法
1. __post_init__:初始化后处理
@dataclass
class Rectangle:
width: float
height: float
area: float = field(init=False) # 不在 __init__ 中
def __post_init__(self):
"""在 __init__ 执行后自动调用"""
if self.width <= 0 or self.height <= 0:
raise ValueError("Width and height must be positive")
self.area = self.width * self.height
rect = Rectangle(10, 20)
print(rect.area) # 200
2. 继承中的 __post_init__
@dataclass
class Base:
x: int
def __post_init__(self):
print(f"Base __post_init__: x={self.x}")
@dataclass
class Derived(Base):
y: int
def __post_init__(self):
super().__post_init__() # 必须显式调用父类方法
print(f"Derived __post_init__: y={self.y}")
d = Derived(10, 20)
# 输出:
# Base __post_init__: x=10
# Derived __post_init__: y=20
五、继承与字段顺序
字段继承规则
@dataclass
class Animal:
species: str
@dataclass
class Dog(Animal):
name: str
age: int = 0
# 字段顺序:父类字段在前,子类字段在后
dog = Dog("犬科", "旺财", 3)
print(dog) # Dog(species='犬科', name='旺财', age=3)
# 错误的字段顺序(子类默认值前不能有父类无默认值字段)
@dataclass
class Cat(Animal):
age: int = 0 # ✅ 正确:有默认值字段在最后
name: str # ❌ 错误:有默认值后不能跟无默认值字段
六、实用函数
1. asdict() 和 astuple()
from dataclasses import asdict, astuple
@dataclass
class Point:
x: int
y: int
p = Point(1, 2)
print(asdict(p)) # {'x': 1, 'y': 2}(递归转换嵌套对象)
print(astuple(p)) # (1, 2)
2. replace():创建修改副本
from dataclasses import replace
p1 = Point(1, 2)
p2 = replace(p1, x=10) # 创建新对象,不修改原对象
print(p1) # Point(x=1, y=2)
print(p2) # Point(x=10, y=2)
# 适用于 frozen=True 的类
@dataclass(frozen=True)
class ImmutablePoint:
x: int
y: int
p = ImmutablePoint(1, 2)
p2 = replace(p, y=5) # ✅ 唯一修改不可变对象的方法
3. fields():获取字段信息
from dataclasses import fields
@dataclass
class Config:
host: str = "localhost"
port: int = 8080
debug: bool = False
for f in fields(Config):
print(f.name, f.type, f.default)
# host <class 'str'> localhost
# port <class 'int'> 8080
# debug <class 'bool'> False
4. is_dataclass():检查是否为 dataclass
from dataclasses import is_dataclass
print(is_dataclass(Point)) # True(类)
print(is_dataclass(Point(1,2))) # True(实例)
print(is_dataclass(str)) # False
七、常见陷阱与解决
陷阱1:可变默认值
# ❌ 错误:所有实例共享同一个列表
@dataclass
class Wrong:
items: list = []
w1 = Wrong()
w2 = Wrong()
w1.items.append(1)
print(w2.items) # [1] ← 意外共享!
# ✅ 正确:使用 default_factory
@dataclass
class Correct:
items: list = field(default_factory=list)
陷阱2:默认值字段位置
# ❌ 错误:有默认值的字段不能放在无默认值字段之前
@dataclass
class Wrong:
age: int = 18 # 有默认值
name: str # 无默认值(SyntaxError)
# ✅ 正确
@dataclass
class Correct:
name: str # 无默认值在前
age: int = 18 # 有默认值在后
陷阱3:类属性污染
# ❌ 错误:类属性会变成默认值
@dataclass
class Wrong:
items: list
items = [] # 这不会产生预期效果
# ✅ 正确:在类级别定义常量
@dataclass
class Correct:
items: list
MAX_SIZE: int = 100 # 类常量(不会成为实例字段)
八、实际应用示例
1. 配置管理
@dataclass(frozen=True)
class DatabaseConfig:
host: str = "localhost"
port: int = 5432
username: str
password: str
database: str
# 部分配置使用默认值
config = DatabaseConfig(
username="admin",
password="secret",
database="myapp"
)
2. API 响应模型
from typing import List, Optional
@dataclass
class Address:
city: str
street: str
zipcode: str
@dataclass
class User:
id: int
name: str
email: str
address: Optional[Address] = None
tags: List[str] = field(default_factory=list)
def __post_init__(self):
if "@" not in self.email:
raise ValueError(f"Invalid email: {self.email}")
3. 树节点结构
@dataclass
class Node:
value: int
left: 'Node' = None
right: 'Node' = None
def __post_init__(self):
if self.value < 0:
raise ValueError("Value must be non-negative")
# 构建二叉树
root = Node(5, Node(3), Node(7))
4. 状态机状态
from enum import Enum
from typing import Any
class State(Enum):
PENDING = "pending"
RUNNING = "running"
COMPLETED = "completed"
FAILED = "failed"
@dataclass
class Task:
name: str
state: State = State.PENDING
result: Any = None
error: Optional[str] = None
retries: int = 0
def complete(self, result):
self.state = State.COMPLETED
self.result = result
def fail(self, error: str):
self.state = State.FAILED
self.error = error
九、dataclass vs 其他方案
| 特性 | dataclass | 普通类 | namedtuple | Pydantic |
|---|---|---|---|---|
| 代码量 | 少 | 多 | 极少 | 少 |
| 可变性 | 可配置 | 可变 | 不可变 | 可配置 |
| 类型提示 | ✅ | 可加 | ❌ | ✅ |
| 运行时验证 | ❌ | 手动 | ❌ | ✅ |
| 序列化 | 手动 | 手动 | 手动 | 内置 |
| JSON 支持 | 需第三方 | 需第三方 | 需第三方 | 内置 |
| 性能 | 好 | 好 | 更好 | 一般 |
| 依赖 | 标准库 | 无 | 标准库 | 第三方 |
十、性能考虑
# dataclass 访问速度
@dataclass
class DataClassPoint:
x: int
y: int
# 普通类访问(类似速度)
class RegularPoint:
__slots__ = ('x', 'y')
def __init__(self, x, y):
self.x = x
self.y = y
# 使用 __slots__ 节省内存
@dataclass
class SlottedPoint:
__slots__ = ('x', 'y')
x: int
y: int
十一、总结
| 要点 | 说明 |
|---|---|
| 核心优势 | 减少样板代码,自动生成特殊方法 |
| 适用场景 | 数据传输对象(DTO)、配置类、值对象 |
| 必须注意 | 可变默认值用 default_factory,字段顺序规则 |
| 扩展能力 | __post_init__ 实现自定义逻辑 |
| 组合使用 | 可与枚举、类型提示、抽象基类等配合 |
dataclass 是 Python 中处理数据类的最佳实践,它让代码更简洁、更安全、更易维护。对于大多数存储数据的类,优先考虑使用 dataclass 而不是手写 __init__ 方法。