2646 字
13 分钟
Python dataclass 完全指南:数据类从入门到精通 | Python 进阶核心知识
Python 3.7 引入的 dataclass 是构建数据类的最佳工具。它自动生成 __init__、__repr__、__eq__ 等方法,大幅减少样板代码,同时保持类型安全和代码可读性。掌握 dataclass 是现代 Python 开发的基本技能。

本文全面讲解 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
@dataclassclass 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 = 301.3 带默认值
from dataclasses import dataclass
@dataclassclass 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@dataclassclass 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
@dataclassclass 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='')
# 访问 metadatafields = Product.__dataclass_fields__print(fields['description'].metadata)# {'max_length': 500, 'description': '产品描述'}2.3 InitVar 和 post_init
from dataclasses import dataclass, field, InitVar
@dataclassclass 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 # FrozenInstanceErrorexcept Exception as e: print(f"Error: {e}")
# 可以比较和哈希(作为字典键或集合元素)p1 = Point(1.0, 2.0)p2 = Point(1.0, 2.0)print(p1 == p2) # Trueprint(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
@dataclassclass BaseUser: id: int name: str email: str
@dataclassclass AdminUser(BaseUser): role: str = "admin" permissions: list = field(default_factory=list)
@dataclassclass 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
@dataclassclass Animal: name: str age: int = 0
@dataclassclass 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, fieldfrom typing import List
@dataclassclass Address: street: str city: str zip_code: str
@dataclassclass Contact: phone: str email: str
@dataclassclass 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 Yorkprint(person.contact.email) # alice@example.com六、slots 优化(Python 3.10+)
6.1 slots=True
from dataclasses import dataclassimport sys
# 传统 dataclass@dataclassclass 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, astupleimport json
@dataclassclass 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'])
# 序列化为 JSONjson_str = json.dumps(asdict(user), ensure_ascii=False)print(json_str)7.2 从字典创建
from dataclasses import dataclass
@dataclassclass 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, asdictfrom typing import Listimport json
@dataclassclass Address: city: str street: str
@dataclassclass 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, fieldfrom typing import Optionalimport json
@dataclassclass DatabaseConfig: host: str = "localhost" port: int = 5432 username: str = "" password: str = "" database: str = "" pool_size: int = 10 timeout: int = 30
@dataclassclass RedisConfig: host: str = "localhost" port: int = 6379 password: str = "" db: int = 0
@dataclassclass 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.comprint(config.redis.host) # redis.example.com8.2 API 请求/响应模型
from dataclasses import dataclass, fieldfrom typing import List, Optionalfrom datetime import datetime
# API 请求模型@dataclassclass CreatePostRequest: title: str content: str tags: List[str] = field(default_factory=list) published: bool = False
# API 响应模型@dataclassclass 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 )
# 错误响应@dataclassclass ErrorResponse: error: str code: int details: Optional[str] = None8.3 事件系统
from dataclasses import dataclass, fieldfrom typing import Any, Callable, Dict, Listfrom 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 dataclassfrom typing import Generic, TypeVar, List
T = TypeVar('T')
@dataclassclass 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 dataclassfrom typing import Optional, List
@dataclassclass 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
# ✅ 正确@dataclassclass 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:子类字段也加默认值@dataclassclass Parent: name: str = ""
@dataclassclass Child(Parent): age: int = 0 # 加默认值
# ✅ 正确方案2:使用 kw_only(Python 3.10+)@dataclassclass 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 其他方案
| 特性 | dataclass | NamedTuple | Pydantic | attrs |
|---|---|---|---|---|
| 标准库 | ✅ | ✅ | ❌ | ❌ |
| 可变性 | 可选 | 不可变 | 可选 | 可选 |
| 类型验证 | ❌ | ❌ | ✅ | 可选 |
| 序列化 | 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/