跳转到内容

Python/dataclass

维基教科书,自由的教学读本

在Python编程中,我们经常需要定义一些“数据容器”——它们主要用来存储数据,而不是实现复杂的业务逻辑。比如配置文件、API请求参数、实验数据等。传统的做法是定义一个类,然后写一堆重复的代码:

# 传统方式:繁琐且容易出错
class Person:
    def __init__(self, name, age, email):
        self.name = name
        self.age = age
        self.email = email
    
    def __repr__(self):
        return f"Person(name={self.name}, age={self.age}, email={self.email})"
    
    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

这种写法有很多问题:

  • 重复代码多:每个类都要写 __init__、__repr__、__eq__ 等方法
  • 容易出错:忘记在 __eq__ 中添加新字段
  • 没有类型检查:传入错误类型也不会报错
  • 难以维护:添加新字段需要修改多处

为了解决这些问题,Python社区提供了两个优秀的解决方案:dataclass(标准库)和 pydantic(第三方库)。在 dataclass 出现之前,开发者们常常使用 namedtuple 或手写大量重复代码。

与pydantic相比,dataclass的性能要强3-7倍以上,适合单纯的大量数据的容器。例如定义一个Point数据类,保存几百万个Point。

Dataclass 的诞生

[编辑]

2016年的Python 3.6引入了“变量注解”(viriable annotation)写法,即:

var1: int = 101

2018年的Python 3.7为了减少样板代码,让数据类的定义更简洁,引入了dataclass。其设计目标是:“让定义数据类像写配置文件一样简单”

dataclass特性:

  • 自动生成常用方法(__init__、__repr__、__eq__ 等)
  • 使用类型注解来声明字段
  • 零依赖,标准库自带
  • 性能优秀

基础用法

[编辑]
from dataclasses import dataclass

@dataclass
class Student:
    name: str
    age: int
    grade: float
    is_active: bool = True  # 默认值

# 创建实例
student = Student(name="张三", age=20, grade=85.5)

print(student)
# 输出: Student(name='张三', age=20, grade=85.5, is_active=True)

# 自动生成的 __eq__ 方法
student2 = Student(name="张三", age=20, grade=85.5)
print(student == student2)  # True

没有数据验证功能

[编辑]
# Dataclass:只是注解,不验证
from dataclasses import dataclass

@dataclass
class DataclassConfig:
    learning_rate: float
    epochs: int

# ❌ 不会报错,但类型错了
config = DataclassConfig(learning_rate="不是浮点数", epochs="不是整数")
print(config.learning_rate)  # "不是浮点数"
print(type(config.learning_rate))  # <class 'str'>

自动生成的方法

[编辑]
  • __init__:构造函数。注意,对于未赋缺省值的字段,如果在构造函数没有通过命名参数或者位置参数赋值初始化,则报错 TypeError: missing required argument 'name'
  • __repr__:字符串表示
  • __eq__:相等性比较
  • __hash__(可选):哈希值

默认值和工厂函数

[编辑]
from dataclasses import dataclass, field
from typing import List

@dataclass
class Course:
    name: str
    credits: int
    # ❌ 错误:可变默认值会被所有实例共享
    # students: List[str] = []  # 危险!
    
    # ✅ 正确:使用 field 和 default_factory
    students: List[str] = field(default_factory=list)
    metadata: dict = field(default_factory=dict)
    layers: List[int] = field(default_factory=lambda: [128, 64])

course1 = Course(name="数学", credits=4)
course2 = Course(name="物理", credits=3)

course1.students.append("张三")
print(course1.students)  # ['张三']
print(course2.students)  # [] ✅ 独立的列表

高级特性

[编辑]
from dataclasses import dataclass, field

@dataclass(frozen=True)  # 不可变对象
class Point:
    x: float
    y: float

p = Point(1.0, 2.0)
# p.x = 3.0  # ❌ 错误:FrozenInstanceError

@dataclass(order=True)  # 支持比较运算。dataclass 会自动生成以下方法(相当于手动实现了这些,按照字段声明先后做“天然比较顺序”):
# __lt__(<)
# __le__(<=)
# __gt__(>)
# __ge__(>=)
# __eq__(==)(默认就有)
# __ne__(!=)(默认就有)
class Score:
    value: int
    
scores = [Score(85), Score(92), Score(78)]
print(sorted(scores))  # 按 value 排序

@dataclass
class Config:
    name: str
    # 不包含在 __init__ 中,但会被初始化
    timestamp: str = field(init=False)
    
    def __post_init__(self):
        from datetime import datetime
        self.timestamp = datetime.now().isoformat()

config = Config(name="test")
print(config.timestamp)  # 自动生成的时间戳

转换为字典

[编辑]
from dataclasses import dataclass, asdict, astuple

@dataclass
class Book:
    title: str
    author: str
    year: int

book = Book("Python编程", "张三", 2023)

# 转为字典
print(asdict(book))
# {'title': 'Python编程', 'author': '张三', 'year': 2023}

# 转为元组
print(astuple(book))
# ('Python编程', '张三', 2023)