2646 字
13 分钟

Python dataclass 完全指南:数据类从入门到精通 | Python 进阶核心知识

Python 3.7 引入的 dataclass 是构建数据类的最佳工具。它自动生成 __init____repr____eq__ 等方法,大幅减少样板代码,同时保持类型安全和代码可读性。掌握 dataclass 是现代 Python 开发的基本技能。

Python dataclass 示意图

本文全面讲解 Python dataclass:

  • @dataclass 装饰器基础用法
  • 字段配置:default / default_factory / field()
  • 不可变数据类(frozen=True
  • 继承与组合
  • __post_init__ 初始化后钩子
  • slots=True 内存优化(Python 3.10+)
  • typing 模块结合
  • 序列化与反序列化
  • 实战场景与最佳实践

一、dataclass 基础#

1.1 为什么需要 dataclass?#

# ❌ 传统写法:大量样板代码
class UserOld:
def __init__(self, name, age, email):
self.name = name
self.age = age
self.email = email
def __repr__(self):
return f"UserOld(name={self.name!r}, age={self.age!r}, email={self.email!r})"
def __eq__(self, other):
if not isinstance(other, UserOld):
return NotImplemented
return (self.name, self.age, self.email) == (other.name, other.age, other.email)
# ✅ dataclass 写法:自动生成上述方法
from dataclasses import dataclass
@dataclass
class User:
name: str
age: int
email: str
# 使用
user1 = User("Alice", 30, "alice@example.com")
user2 = User("Alice", 30, "alice@example.com")
print(user1) # User(name='Alice', age=30, email='alice@example.com')
print(user1 == user2) # True(自动生成 __eq__)

1.2 @dataclass 参数#

from dataclasses import dataclass
@dataclass(
init=True, # 生成 __init__(默认 True)
repr=True, # 生成 __repr__(默认 True)
eq=True, # 生成 __eq__(默认 True)
order=False, # 生成 __lt__/__le__/__gt__/__ge__(默认 False)
unsafe_hash=False, # 生成 __hash__(默认 False)
frozen=False, # 不可变(默认 False)
match_args=True, # 生成 __match_args__(Python 3.10+,默认 True)
slots=False, # 使用 __slots__(Python 3.10+,默认 False)
kw_only=False, # 关键字参数模式(Python 3.10+,默认 False)
)
class Config:
debug: bool = False
timeout: int = 30

1.3 带默认值#

from dataclasses import dataclass
@dataclass
class Server:
host: str = "localhost"
port: int = 8080
debug: bool = False
max_connections: int = 100
# 使用默认值
server = Server()
print(server) # Server(host='localhost', port=8080, debug=False, max_connections=100)
# 部分赋值
server = Server(port=3000, debug=True)
print(server) # Server(host='localhost', port=3000, debug=True, max_connections=100)

二、字段配置:field()#

2.1 default vs default_factory#

from dataclasses import dataclass, field
# ❌ 错误:可变默认值
# @dataclass
# class BadExample:
# items: list = [] # ValueError: mutable default not allowed
# ✅ 正确:使用 default_factory
@dataclass
class GoodExample:
items: list = field(default_factory=list) # 每次创建新列表
tags: set = field(default_factory=set) # 每次创建新集合
metadata: dict = field(default_factory=dict) # 每次创建新字典
obj1 = GoodExample()
obj2 = GoodExample()
obj1.items.append("item1")
print(obj1.items) # ['item1']
print(obj2.items) # [] (独立的新列表)

2.2 field() 完整参数#

from dataclasses import dataclass, field
@dataclass
class Product:
name: str
price: float
# default:不可变默认值
category: str = "general"
# default_factory:可变默认值
tags: list = field(default_factory=list)
# init:是否在 __init__ 中包含
internal_id: int = field(default=0, init=False)
# repr:是否在 __repr__ 中显示
secret_key: str = field(default="", repr=False)
# compare:是否参与比较
created_at: str = field(default="", compare=False)
# metadata:自定义元数据
description: str = field(
default="",
metadata={"max_length": 500, "description": "产品描述"}
)
# 使用
product = Product(name="Laptop", price=999.99, tags=["electronics"])
print(product)
# Product(name='Laptop', price=999.99, category='general', tags=['electronics'],
# internal_id=0, created_at='', description='')
# 访问 metadata
fields = Product.__dataclass_fields__
print(fields['description'].metadata)
# {'max_length': 500, 'description': '产品描述'}

2.3 InitVar 和 post_init#

from dataclasses import dataclass, field, InitVar
@dataclass
class DatabaseConfig:
host: str
port: int
# InitVar:仅用于 __init__ 传递,不存储为实例属性
password: InitVar[str] = ""
# 连接字符串在 __post_init__ 中生成
connection_string: str = field(init=False)
def __post_init__(self, password: str):
"""__init__ 执行后调用"""
if password:
self.connection_string = f"postgresql://{self.host}:{self.port}?password={password}"
else:
self.connection_string = f"postgresql://{self.host}:{self.port}"
# 使用
db = DatabaseConfig("localhost", 5432, password="secret123")
print(db.connection_string)
# postgresql://localhost:5432?password=secret123
# password 不存储为实例属性
print(hasattr(db, 'password')) # False

三、不可变数据类#

3.1 frozen=True#

from dataclasses import dataclass
@dataclass(frozen=True)
class Point:
x: float
y: float
p = Point(1.0, 2.0)
# 不可修改
try:
p.x = 3.0 # FrozenInstanceError
except Exception as e:
print(f"Error: {e}")
# 可以比较和哈希(作为字典键或集合元素)
p1 = Point(1.0, 2.0)
p2 = Point(1.0, 2.0)
print(p1 == p2) # True
print(hash(p1)) # 可哈希
# 作为字典键
points = {p1: "origin"}
print(points[Point(1.0, 2.0)]) # "origin"

3.2 不可变数据类的应用#

from dataclasses import dataclass
@dataclass(frozen=True)
class Color:
r: int
g: int
b: int
def to_hex(self) -> str:
return f"#{self.r:02x}{self.g:02x}{self.b:02x}"
@dataclass(frozen=True)
class Config:
host: str
port: int
debug: bool = False
@property
def url(self) -> str:
return f"http://{self.host}:{self.port}"
# 不可变配置对象
config = Config("example.com", 443)
print(config.url) # http://example.com:443
# 颜色对象
red = Color(255, 0, 0)
print(red.to_hex()) # #ff0000

四、排序与比较#

4.1 order=True#

from dataclasses import dataclass
@dataclass(order=True)
class Student:
# 按分数排序(降序需要反向)
score: float
name: str
age: int = 18
students = [
Student(85.5, "Alice"),
Student(92.0, "Bob"),
Student(78.0, "Charlie", 20),
Student(92.0, "David", 19),
]
# 排序
sorted_students = sorted(students)
for s in sorted_students:
print(s)
# Student(score=78.0, name='Charlie', age=20)
# Student(score=85.5, name='Alice', age=18)
# Student(score=92.0, name='Bob', age=18) # 分数相同时按 name 排序
# Student(score=92.0, name='David', age=19)
# 比较
s1 = Student(90.0, "Alice")
s2 = Student(85.0, "Bob")
print(s1 > s2) # True(按 score 比较)

4.2 排除某些字段#

from dataclasses import dataclass, field
@dataclass(order=True)
class Priority:
# 只按 priority 排序
priority: int
name: str = field(compare=False)
description: str = field(compare=False)
tasks = [
Priority(3, "Low", "可选任务"),
Priority(1, "High", "紧急任务"),
Priority(2, "Medium", "普通任务"),
]
for task in sorted(tasks):
print(f"{task.priority}: {task.name}")
# 1: High
# 2: Medium
# 3: Low

五、继承与组合#

5.1 dataclass 继承#

from dataclasses import dataclass, field
@dataclass
class BaseUser:
id: int
name: str
email: str
@dataclass
class AdminUser(BaseUser):
role: str = "admin"
permissions: list = field(default_factory=list)
@dataclass
class GuestUser(BaseUser):
expires_at: str = ""
# 使用
admin = AdminUser(
id=1,
name="Alice",
email="alice@example.com",
permissions=["read", "write", "delete"]
)
print(admin)
# AdminUser(id=1, name='Alice', email='alice@example.com',
# role='admin', permissions=['read', 'write', 'delete'])
guest = GuestUser(id=2, name="Bob", email="bob@example.com", expires_at="2026-12-31")
print(guest)
# GuestUser(id=2, name='Bob', email='bob@example.com', expires_at='2026-12-31')

5.2 带默认值的继承#

from dataclasses import dataclass
@dataclass
class Animal:
name: str
age: int = 0
@dataclass
class Dog(Animal):
# 子类新增字段必须有默认值(如果父类字段有默认值)
breed: str = "Unknown"
tricks: list = field(default_factory=list)
# 正确:所有参数都有默认值
dog = Dog("Buddy")
print(dog) # Dog(name='Buddy', age=0, breed='Unknown', tricks=[])
# 带所有参数
dog = Dog("Buddy", 3, "Golden Retriever", ["sit", "roll"])
print(dog)

5.3 组合模式#

from dataclasses import dataclass, field
from typing import List
@dataclass
class Address:
street: str
city: str
zip_code: str
@dataclass
class Contact:
phone: str
email: str
@dataclass
class Person:
name: str
age: int
address: Address # 组合
contact: Contact # 组合
tags: List[str] = field(default_factory=list)
# 使用
person = Person(
name="Alice",
age=30,
address=Address("123 Main St", "New York", "10001"),
contact=Contact("555-1234", "alice@example.com"),
tags=["friend", "colleague"]
)
print(person.address.city) # New York
print(person.contact.email) # alice@example.com

六、slots 优化(Python 3.10+)#

6.1 slots=True#

from dataclasses import dataclass
import sys
# 传统 dataclass
@dataclass
class PointDict:
x: float
y: float
# slots 优化
@dataclass(slots=True)
class PointSlots:
x: float
y: float
# 内存对比
p1 = PointDict(1.0, 2.0)
p2 = PointSlots(1.0, 2.0)
print(sys.getsizeof(p1.__dict__)) # ~104 bytes(字典开销)
# PointSlots 没有 __dict__
print(hasattr(p2, '__dict__')) # False
# slots 优势:
# 1. 更低的内存占用(约 40-50%)
# 2. 更快的属性访问
# 3. 防止动态添加属性

6.2 性能对比#

import timeit
# 访问性能对比
p_dict = PointDict(1.0, 2.0)
p_slots = PointSlots(1.0, 2.0)
time_dict = timeit.timeit(lambda: p_dict.x, number=10_000_000)
time_slots = timeit.timeit(lambda: p_slots.x, number=10_000_000)
print(f"Dict: {time_dict:.3f}s")
print(f"Slots: {time_slots:.3f}s")
print(f"Speedup: {time_dict / time_slots:.2f}x")
# Slots 通常快 20-30%

七、序列化与反序列化#

7.1 转换为字典#

from dataclasses import dataclass, asdict, astuple
import json
@dataclass
class User:
name: str
age: int
email: str
tags: list = None
user = User("Alice", 30, "alice@example.com", ["admin", "user"])
# 转字典
user_dict = asdict(user)
print(user_dict)
# {'name': 'Alice', 'age': 30, 'email': 'alice@example.com', 'tags': ['admin', 'user']}
# 转元组
user_tuple = astuple(user)
print(user_tuple)
# ('Alice', 30, 'alice@example.com', ['admin', 'user'])
# 序列化为 JSON
json_str = json.dumps(asdict(user), ensure_ascii=False)
print(json_str)

7.2 从字典创建#

from dataclasses import dataclass
@dataclass
class User:
name: str
age: int
email: str
# 从字典创建
data = {"name": "Alice", "age": 30, "email": "alice@example.com"}
user = User(**data)
print(user) # User(name='Alice', age=30, email='alice@example.com')
# 从 JSON 创建
json_str = '{"name": "Bob", "age": 25, "email": "bob@example.com"}'
data = json.loads(json_str)
user = User(**data)
print(user)

7.3 嵌套序列化#

from dataclasses import dataclass, asdict
from typing import List
import json
@dataclass
class Address:
city: str
street: str
@dataclass
class User:
name: str
address: Address
friends: List[str]
# 创建嵌套对象
user = User(
name="Alice",
address=Address("New York", "5th Avenue"),
friends=["Bob", "Charlie"]
)
# asdict 递归转换
user_dict = asdict(user)
print(json.dumps(user_dict, indent=2, ensure_ascii=False))
# {
# "name": "Alice",
# "address": {
# "city": "New York",
# "street": "5th Avenue"
# },
# "friends": ["Bob", "Charlie"]
# }

八、实战场景#

8.1 配置管理#

from dataclasses import dataclass, field
from typing import Optional
import json
@dataclass
class DatabaseConfig:
host: str = "localhost"
port: int = 5432
username: str = ""
password: str = ""
database: str = ""
pool_size: int = 10
timeout: int = 30
@dataclass
class RedisConfig:
host: str = "localhost"
port: int = 6379
password: str = ""
db: int = 0
@dataclass
class AppConfig:
debug: bool = False
database: DatabaseConfig = field(default_factory=DatabaseConfig)
redis: RedisConfig = field(default_factory=RedisConfig)
@classmethod
def from_json(cls, json_str: str) -> 'AppConfig':
data = json.loads(json_str)
db_data = data.get('database', {})
redis_data = data.get('redis', {})
return cls(
debug=data.get('debug', False),
database=DatabaseConfig(**db_data),
redis=RedisConfig(**redis_data)
)
# 从 JSON 加载配置
config_json = '''
{
"debug": true,
"database": {
"host": "db.example.com",
"port": 5432,
"username": "admin",
"database": "myapp"
},
"redis": {
"host": "redis.example.com"
}
}
'''
config = AppConfig.from_json(config_json)
print(config.database.host) # db.example.com
print(config.redis.host) # redis.example.com

8.2 API 请求/响应模型#

from dataclasses import dataclass, field
from typing import List, Optional
from datetime import datetime
# API 请求模型
@dataclass
class CreatePostRequest:
title: str
content: str
tags: List[str] = field(default_factory=list)
published: bool = False
# API 响应模型
@dataclass
class PostResponse:
id: int
title: str
content: str
tags: List[str]
published: bool
created_at: str
author: str
@classmethod
def from_model(cls, post) -> 'PostResponse':
"""从数据库模型转换"""
return cls(
id=post.id,
title=post.title,
content=post.content,
tags=post.tags,
published=post.published,
created_at=post.created_at.isoformat(),
author=post.author.name
)
# 错误响应
@dataclass
class ErrorResponse:
error: str
code: int
details: Optional[str] = None

8.3 事件系统#

from dataclasses import dataclass, field
from typing import Any, Callable, Dict, List
from datetime import datetime
# 事件基类
@dataclass(frozen=True)
class Event:
timestamp: str = field(default_factory=lambda: datetime.now().isoformat())
@property
def event_type(self) -> str:
return self.__class__.__name__
# 具体事件
@dataclass(frozen=True)
class UserCreated(Event):
user_id: int
name: str
email: str
@dataclass(frozen=True)
class OrderPlaced(Event):
order_id: int
user_id: int
total: float
items: List[dict] = field(default_factory=list)
# 事件处理器
class EventBus:
def __init__(self):
self._handlers: Dict[str, List[Callable]] = {}
def subscribe(self, event_type: str, handler: Callable):
self._handlers.setdefault(event_type, []).append(handler)
def publish(self, event: Event):
event_type = event.event_type
for handler in self._handlers.get(event_type, []):
handler(event)
# 使用
bus = EventBus()
def send_welcome_email(event: UserCreated):
print(f"发送欢迎邮件给 {event.name} <{event.email}>")
bus.subscribe("UserCreated", send_welcome_email)
# 发布事件
event = UserCreated(user_id=1, name="Alice", email="alice@example.com")
bus.publish(event)
# 发送欢迎邮件给 Alice <alice@example.com>

九、与 typing 深度结合#

9.1 泛型 dataclass#

from dataclasses import dataclass
from typing import Generic, TypeVar, List
T = TypeVar('T')
@dataclass
class Result(Generic[T]):
data: T
success: bool = True
error: str = ""
# 使用
result1: Result[int] = Result(data=42)
result2: Result[str] = Result(data="hello")
result3: Result[List[str]] = Result(data=["a", "b"])
print(result1) # Result(data=42, success=True, error='')
print(result2) # Result(data='hello', success=True, error='')

9.2 Optional 和默认值#

from dataclasses import dataclass
from typing import Optional, List
@dataclass
class UserProfile:
username: str
email: str
# Optional 字段默认 None
avatar: Optional[str] = None
bio: Optional[str] = None
# 可变默认值用 default_factory
followers: List[str] = field(default_factory=list)
def __post_init__(self):
# 后处理:设置默认头像
if self.avatar is None:
self.avatar = f"https://example.com/avatars/default.png"
profile = UserProfile(username="alice", email="alice@example.com")
print(profile.avatar) # https://example.com/avatars/default.png

十、最佳实践与陷阱#

❌ 陷阱 1:可变默认值#

from dataclasses import dataclass, field
# ❌ 错误
# @dataclass
# class Bad:
# items: list = [] # ValueError
# ✅ 正确
@dataclass
class Good:
items: list = field(default_factory=list)

❌ 陷阱 2:继承中有默认值字段后跟无默认值字段#

from dataclasses import dataclass
# ❌ 错误:父类有默认值,子类新字段不能无默认值
# @dataclass
# class Parent:
# name: str = ""
#
# @dataclass
# class Child(Parent):
# age: int # TypeError: non-default argument follows default argument
# ✅ 正确方案1:子类字段也加默认值
@dataclass
class Parent:
name: str = ""
@dataclass
class Child(Parent):
age: int = 0 # 加默认值
# ✅ 正确方案2:使用 kw_only(Python 3.10+)
@dataclass
class ChildKW(Parent):
age: int = field(kw_only=True, default=0)

✅ 最佳实践#

原则说明
可变默认值用 default_factory避免共享引用
不可变数据用 frozen=True线程安全、可哈希
大数据量用 slots=True节省内存(Python 3.10+)
post_init 做验证初始化后校验
配合类型注解提升可读性和 IDE 支持
asdict 做序列化递归转换嵌套对象
InitVar 传临时参数不存储为实例属性

dataclass vs 其他方案#

特性dataclassNamedTuplePydanticattrs
标准库
可变性可选不可变可选可选
类型验证可选
序列化asdict_asdict内置内置
性能最好一般
Python版本3.7+3.6+3.6+3.4+

选择建议

  • 简单数据容器 → dataclass
  • 不可变轻量数据 → NamedTuple
  • API 开发需要验证 → Pydantic
  • 复杂验证和转换 → attrs

dataclass 是现代 Python 数据建模的首选方案。它简洁、标准、灵活,从简单配置到复杂事件系统都能胜任。掌握 dataclass,让代码更 Pythonic、更易维护。

Python dataclass 完全指南:数据类从入门到精通 | Python 进阶核心知识
https://971918.xyz/posts/python-guide/python-dataclass-guide/
作者
九所长
发布于
2026-06-26
许可协议
CC BY-NC-SA 4.0