Python dataclass 实战:从 dict 到声明式数据模型
项目里到处都是 dict,读取数据时 data['name'],写错了 key 又不报错。上线后排查了半天,发现是 key 拼写错了。后来用 dataclass 替换 dict,IDE 能正确补全,写错属性名直接报错。
为什么不用 dict
# 典型的 dict 用法
user = {
"name": "张三",
"age": 25,
"email": "zhangsan@example.com"
}
# 读取数据
name = user["name"] # 正常
name = user["namee"] # KeyError!运行时才发现
# 传参时
def send_email(user):
print(f"发送邮件到 {user['emial']}") # 拼写错误,运行时才发现
问题:
- 没有类型提示:IDE 不知道 user 有哪些字段
- 拼写错误不报错:
user['emial']运行时才报 KeyError - 没有默认值:每个字段都要手动赋值
- 没有比较方法:两个 dict 比较需要手动实现
dataclass 基础用法
最简配置
from dataclasses import dataclass
@dataclass
class User:
name: str
age: int
email: str
user = User(name="张三", age=25, email="zhangsan@example.com")
print(user) # User(name='张三', age=25, email='zhangsan@example.com')
带默认值
@dataclass
class User:
name: str
age: int = 0
email: str = ""
is_active: bool = True
user = User(name="张三")
print(user) # User(name='张三', age=0, email='', is_active=True)
坑在于:默认值必须放在没有默认值的字段后面。
从 dict 创建
data = {"name": "张三", "age": 25, "email": "zhangsan@example.com"}
user = User(**data)
print(user.name) # 张三
进阶用法
字段选项
from dataclasses import dataclass, field
@dataclass
class User:
name: str
age: int = field(default=0, metadata={"min": 0, "max": 150})
email: str = field(default="", repr=False) # repr=False 不在打印时显示
tags: list = field(default_factory=list) # 可变默认值要用 default_factory
user = User(name="张三")
print(user) # User(name='张三', age=0, tags=[])
坑在于:可变对象(list、dict)不能直接作为默认值,要用 default_factory。
frozen dataclass
@dataclass(frozen=True)
class Point:
x: float
y: float
p1 = Point(1.0, 2.0)
# p1.x = 3.0 # 报错:FrozenInstanceError
frozen dataclass 不可变,可以用作 dict 的 key。
继承
@dataclass
class Animal:
name: str
age: int
@dataclass
class Dog(Animal):
breed: str = "unknown"
dog = Dog(name="旺财", age=3, breed="柴犬")
print(dog) # Dog(name='旺财', age=3, breed='柴犬')
与 Pydantic 的对比
from dataclasses import dataclass
from pydantic import BaseModel
# dataclass
@dataclass
class UserDC:
name: str
age: int
# Pydantic
class UserPydantic(BaseModel):
name: str
age: int
# dataclass 不做类型验证
user_dc = UserDC(name="张三", age="二十五") # 不报错
# Pydantic 做类型验证
user_pydantic = UserPydantic(name="张三", age="二十五") # 报错
选择建议:
- dataclass:标准库,轻量,适合内部数据传递
- Pydantic:第三方库,有类型验证,适合 API 参数校验
实战:配置管理
from dataclasses import dataclass, field
from typing import List
@dataclass
class DatabaseConfig:
host: str = "localhost"
port: int = 5432
user: str = "postgres"
password: str = ""
db_name: str = "app"
@dataclass
class AppConfig:
debug: bool = False
database: DatabaseConfig = field(default_factory=DatabaseConfig)
allowed_hosts: List[str] = field(default_factory=list)
config = AppConfig(debug=True, database=DatabaseConfig(host="db.example.com"))
print(config.database.host) # db.example.com
实战:API 响应模型
from dataclasses import dataclass, asdict
from typing import Optional
@dataclass
class ApiResponse:
code: int
message: str
data: Optional[dict] = None
def success(data=None):
return ApiResponse(code=200, message="success", data=data)
def error(message):
return ApiResponse(code=500, message=message)
# 转换为 dict
response = success({"user": "张三"})
print(asdict(response)) # {'code': 200, 'message': 'success', 'data': {'user': '张三'}}
踩坑总结
- 可变默认值(list、dict)要用
field(default_factory=...) - frozen dataclass 不可变,可以用作 dict 的 key
- dataclass 不做类型验证,需要验证用 Pydantic
asdict()可以把 dataclass 转换为 dict- dataclass 是标准库,Python 3.7+ 可用
从 dict 到 dataclass,只需要加一个 @dataclass 装饰器,但能获得类型提示、IDE 补全、自动 __repr__ 和 __eq__。坑在于:很多人不知道 default_factory 这个坑。