项目里到处都是 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']}")  # 拼写错误,运行时才发现

问题:

  1. 没有类型提示:IDE 不知道 user 有哪些字段
  2. 拼写错误不报错user['emial'] 运行时才报 KeyError
  3. 没有默认值:每个字段都要手动赋值
  4. 没有比较方法:两个 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': '张三'}}

踩坑总结

  1. 可变默认值(list、dict)要用 field(default_factory=...)
  2. frozen dataclass 不可变,可以用作 dict 的 key
  3. dataclass 不做类型验证,需要验证用 Pydantic
  4. asdict() 可以把 dataclass 转换为 dict
  5. dataclass 是标准库,Python 3.7+ 可用

从 dict 到 dataclass,只需要加一个 @dataclass 装饰器,但能获得类型提示、IDE 补全、自动 __repr____eq__。坑在于:很多人不知道 default_factory 这个坑。

标签: Python

添加新评论