dataclasses是 Python 3.7 引入的标准库模块,到 2026 年已经非常成熟。它解决了 Python 类定义中一个长期存在的痛点:写大量重复的__init__、__repr__、__eq__等样板代码。
但很多人只是用它来省几行代码,实际上dataclasses的能力远不止于此。
一、为什么需要 dataclass?
先看一个普通的 Python 类:
python
class Person: def __init__(self, name: str, age: int, email: str = ""): self.name = name self.age = age self.email = email def __repr__(self): return f"Person(name={self.name!r}, age={self.age!r}, email={self.email!r})" def __eq__(self, other): if not isinstance(other, Person): return False return self.name == other.name and self.age == other.age and self.email == other.email # 10 行代码只定义了一个数据容器用dataclass改写:
python
from dataclasses import dataclass @dataclass class Person: name: str age: int email: str = "" # 自动获得 __init__、__repr__、__eq__、__hash__(如果 frozen=True)
整整少了 10 行样板代码,而且类型更清晰、更可读。
二、基础用法
2.1 字段定义与默认值
python
from dataclasses import dataclass from typing import Optional @dataclass class User: id: int username: str email: str is_active: bool = True age: Optional[int] = None # 可选字段 tags: list[str] = None # ⚠️ 注意:不要用 [] 作为默认值! def __post_init__(self): """初始化后处理:用于依赖其他字段的默认值""" if self.tags is None: self.tags = []
重要:不要用可变对象作为默认值(如[]、{}、set())。这和普通函数的默认参数规则一致——所有实例共享同一个列表。
python
# ❌ 错误写法 @dataclass class Bad: items: list = [] # 所有实例共享同一个 list # ✅ 正确写法:用 field(default_factory=list) from dataclasses import field @dataclass class Good: items: list = field(default_factory=list)
2.2 field() 高级配置
field()提供了精细控制每个字段的能力:
python
from dataclasses import dataclass, field @dataclass class Product: id: int name: str price: float = field(default=0.0) # 从 __init__ 中排除(不作为构造参数) created_at: str = field(default_factory=lambda: "2026-01-01", init=False) # 不参与比较(__eq__ 忽略此字段) cache_key: str = field(default="", compare=False) # 不参与 repr internal_id: int = field(default=0, repr=False) # 元数据(不会被 dataclass 使用,仅供开发者附加信息) metadata: dict = field(default_factory=dict, metadata={"description": "附加信息"}) p = Product(1, "手机", 2999.0) print(p) # Product(id=1, name='手机', price=2999.0, created_at='2026-01-01') # cache_key 和 internal_id 不在 __init__ 参数中field()参数速查:
| 参数 | 作用 |
|---|---|
default | 默认值 |
default_factory | 生成默认值的可调用对象 |
init | 是否为__init__参数 |
repr | 是否包含在__repr__中 |
compare | 是否参与比较(__eq__等) |
hash | 是否参与哈希计算 |
metadata | 附加元数据,不被 dataclass 使用 |
三、进阶功能
3.1post_init:初始化后处理
__post_init__在__init__执行后调用,适合做依赖其他字段的初始化或校验:
python
from dataclasses import dataclass, field from datetime import datetime @dataclass class Order: order_id: str items: list created_at: datetime = field(init=False) total_price: float = field(init=False) def __post_init__(self): # 自动设置创建时间 self.created_at = datetime.now() # 自动计算总价 self.total_price = sum(item["price"] * item["quantity"] for item in self.items) # 校验 if not self.order_id.startswith("ORD-"): raise ValueError("订单号必须以 ORD- 开头") order = Order("ORD-123", [{"price": 100, "quantity": 2}]) print(order.total_price) # 2003.2 frozen=True:不可变数据类
frozen=True让实例变成只读的,类似不可变对象:
python
@dataclass(frozen=True) class Point: x: int y: int p = Point(1, 2) # p.x = 3 # ❌ FrozenInstanceError: cannot assign to field 'x' print(p) # Point(x=1, y=2)
使用场景:配置对象、值对象(Value Object)、作为字典键使用(自动生成__hash__)。
3.3 order=True:自动支持排序
order=True自动生成__lt__、__le__、__gt__、__ge__方法,按字段定义顺序比较:
python
@dataclass(order=True) class Score: value: int name: str scores = [Score(80, "张三"), Score(95, "李四"), Score(70, "王五")] sorted_scores = sorted(scores) print([s.name for s in sorted_scores]) # ['王五', '张三', '李四']
如果想自定义排序字段,用field(compare=True/False)控制。
3.4 继承
dataclass支持继承,但有一些注意事项:
python
@dataclass class Base: id: int @dataclass class User(Base): name: str age: int # 子类字段会在基类字段之后 u = User(1, "张三", 25) print(u) # User(id=1, name='张三', age=25)
注意事项:
子类会在基类字段之后追加字段
如果子类有默认值而基类没有,会报错(默认值字段不能出现在非默认值字段之前)
python
# ❌ 编译错误 @dataclass class Base: id: int @dataclass class Child(Base): name: str = "default" # 基类 id 没有默认值,子类 name 有默认值 → 报错 age: int
解决方案:@dataclass也支持设置默认值,但必须严格遵守顺序。
四、实战场景
场景一:配置管理类
python
from dataclasses import dataclass, field from typing import Optional @dataclass class DatabaseConfig: host: str = "localhost" port: int = 3306 username: str password: str database: str = "app_db" pool_size: int = 10 timeout: int = 30 @property def connection_url(self) -> str: return f"mysql://{self.username}:{self.password}@{self.host}:{self.port}/{self.database}" @dataclass class AppConfig: debug: bool = False database: DatabaseConfig api_key: Optional[str] = None def __post_init__(self): if self.debug and not self.api_key: # 开发模式需要某些特殊处理 pass # 使用 db_conf = DatabaseConfig(username="root", password="123456") app_conf = AppConfig(debug=True, database=db_conf) print(app_conf.database.connection_url)场景二:API 响应对象
python
from dataclasses import dataclass, field from typing import Optional, List from datetime import datetime @dataclass class UserDTO: id: int username: str email: str created_at: datetime is_active: bool = True @classmethod def from_dict(cls, data: dict) -> "UserDTO": """从 API 响应构造对象""" return cls( id=data["id"], username=data["login"], email=data["email"], created_at=datetime.fromisoformat(data["created_at"]), is_active=data.get("active", True) ) @dataclass class ApiResponse: status: int data: UserDTO message: str = "" errors: List[str] = field(default_factory=list) @property def is_success(self) -> bool: return 200 <= self.status < 300 # 使用 response_data = { "id": 1, "login": "zhangsan", "email": "zhangsan@example.com", "created_at": "2026-07-21T10:30:00", "active": True } user = UserDTO.from_dict(response_data) print(user) # UserDTO(id=1, username='zhangsan', email='zhangsan@example.com', ...)场景三:数据校验(结合post_init)
python
from dataclasses import dataclass, field import re @dataclass class UserRegistration: username: str email: str password: str confirm_password: str def __post_init__(self): # 用户名长度校验 if len(self.username) < 3 or len(self.username) > 20: raise ValueError("用户名长度必须在 3-20 之间") # 邮箱格式校验 if not re.match(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$", self.email): raise ValueError("邮箱格式无效") # 密码复杂度校验 if len(self.password) < 8: raise ValueError("密码长度至少 8 位") # 密码确认 if self.password != self.confirm_password: raise ValueError("两次密码输入不一致")五、dataclass 与slots配合
默认情况下,dataclass 实例有__dict__,内存占用较大。如果需要大量实例,可以用__slots__优化:
python
from dataclasses import dataclass @dataclass class PointWithSlots: __slots__ = ("x", "y") # 必须在使用 dataclass 之前定义 x: int y: int p = PointWithSlots(1, 2) # p.z = 3 # ❌ AttributeError: 'PointWithSlots' object has no attribute 'z'注意:__slots__必须在类体中位于@dataclass之前,否则 Python 会忽略它。
六、序列化与反序列化
使用dataclasses.asdict/astuple
python
from dataclasses import dataclass, asdict, astuple @dataclass class User: id: int name: str age: int u = User(1, "张三", 25) print(asdict(u)) # {'id': 1, 'name': '张三', 'age': 25} print(astuple(u)) # (1, '张三', 25)与 JSON 互转
python
import json from dataclasses import dataclass, asdict @dataclass class Product: id: int name: str price: float p = Product(1, "手机", 2999.0) # 序列化 json_str = json.dumps(asdict(p), ensure_ascii=False) print(json_str) # {"id": 1, "name": "手机", "price": 2999.0} # 反序列化 data = json.loads(json_str) p2 = Product(**data) print(p2) # Product(id=1, name='手机', price=2999.0)七、dataclass vs Pydantic vs attrs
到了 2026 年,这三个库各有定位:
| 对比维度 | dataclass | Pydantic | attrs |
|---|---|---|---|
| 标准库 | ✅ 是 | ❌ 需安装 | ❌ 需安装 |
| 运行校验 | ❌ 手动实现 | ✅ 自动 | ❌ 手动实现 |
| 序列化 | 手动转 dict | 内置 | 需插件 |
| 性能 | 高 | 中等 | 高 |
| 适用场景 | 内部数据类 | API/配置/入参校验 | 高性能场景 |
选择建议:
内部数据容器:
dataclass够用且无依赖API 入参/出参、配置文件:用 Pydantic(自带校验)
追求极致性能:用
attrs(但attrs与 dataclass 性能差距在 3.12+ 已很小)
八、总结
dataclasses模块的核心价值是用装饰器语法自动生成样板代码,同时提供了足够的扩展点(field、__post_init__、frozen、order)。
| 特性 | 用途 |
|---|---|
| 基础 | 替代手写__init__、__repr__、__eq__ |
field() | 精细控制每个字段的行为 |
__post_init__ | 依赖其他字段的初始化和校验 |
frozen=True | 创建不可变对象 |
order=True | 自动支持排序 |
| 继承 | 构建类的层次结构 |
本文为纯技术分享,不涉及任何品牌或产品。