小白学 Python
课程GitHub
© 2026 小白学 Python · 基于 walter201230/Python 教程
课程目录GitHub
Python 环境加载中…
dataclass0 / 4
1234

dataclass

4 道练习题·预计 35 分钟·做对一题解锁下一段

教学 01 / 05· 已读

第二十节:dataclass

写代码这事,一不留神就会陷入「样板代码」的泥潭。先做个小实验:现在请你写一个 Employee 类,要求是这样的——存员工的姓名、部门、工号、入职日期、月薪,要能正常打印(不能是 <__main__.Employee object at 0x...> 这种鬼东西),要能比较两个员工对象是否相等,最好还能拿出来做字典的 key。

听起来不难是吧?大概长这样:

python
class Employee:
    def __init__(self, name, dept, emp_id, hire_date, salary):
        self.name = name
        self.dept = dept
        self.emp_id = emp_id
        self.hire_date = hire_date
        self.salary = salary

    def __repr__(self):
        return (
            f'Employee(name={self.name!r}, dept={self.dept!r}, '
            f'emp_id={self.emp_id!r}, hire_date={self.hire_date!r}, '
            f'salary={self.salary!r})'
        )

    def __eq__(self, other):
        if not isinstance(other, Employee):
            return NotImplemented
        return (
            self.name == other.name
            and self.dept == other.dept
            and self.emp_id == other.emp_id
            and self.hire_date == other.hire_date
            and self.salary == other.salary
        )

    def __hash__(self):
        return hash((self.name, self.dept, self.emp_id, self.hire_date, self.salary))

数一下,光这么一个普普通通的「数据类」,就花了二十多行。__init__ 写一遍字段,__repr__ 写一遍字段,__eq__ 写一遍字段,__hash__ 又写一遍字段——同一组字段名重复出现了五次。再想象一下你这个类有 15 个字段,那 __init__ 的参数列表就要排成一列火车,每个 self.xxx = xxx 都要复制粘贴,写到第十个就开始想骂人。

这就是「样板代码」

它没创造任何业务价值,纯粹是 Python 语法要求你必须这么写。写代码的人讨厌它,看代码的人也讨厌它,因为信息密度太低,真正重要的「这个类有哪些字段」被淹没在 self.xxx = xxx 的重复噪声里。

那有没有什么办法,能让我们只声明字段,剩下的活儿让 Python 自己干?

有。Python 3.7 给我们送来了 dataclass。从 3.10 起又给它加了 slots、kw_only 等更现代的开关。这一节,我们就把 dataclass 这条线从基础用法一路捋到现代写法,让各位写数据结构的时候,再也不用手指头打结。

这一节要学什么

  • 基础:@dataclass 三行代码搞定 __init__ / __repr__ / __eq__
  • 默认值:普通默认值 + field(default_factory=...) 处理可变默认值
  • frozen:让对象不可变,副产品是能进 set、能当 dict key
  • 比较:__eq__ 自动按字段值比较

学完之后,再写数据类基本上就是「贴一个装饰器、列一下字段」这种轻松活儿。

教学 02 / 05

一、第一个 dataclass

把上面那个 Employee 类,用 dataclass 重写一遍:

python
from dataclasses import dataclass


@dataclass
class Employee:
    name: str
    dept: str
    emp_id: int
    hire_date: str
    salary: int


e = Employee('两点水', '研发部', 1001, '2020-03-15', 12000)
print(e)

输出:

Employee(name='两点水', dept='研发部', emp_id=1001, hire_date='2020-03-15', salary=12000)

二十多行的代码,缩成了不到十行。@dataclass 这个装饰器一贴,Python 就帮我们干了这些事:

  • 看到 name: str、dept: str 这些「带类型注解的类变量」,自动当成字段
  • 自动生成一个 __init__,参数顺序就是字段顺序
  • 自动生成一个 __repr__,长得跟咱们手写的那种「类名(字段=值, 字段=值)」一模一样
  • 自动生成一个 __eq__,按字段逐个比较

整个过程,你只需要把字段名和它的类型写出来,剩下的全是 dataclass 在帮你干活。

类型注解必须有

那「类型注解」是不是必须的?是的。这是 dataclass 识别字段的依据。你如果只写 name = '' 而不写 name: str,dataclass 就认不出来——它会把 name 当成一个普通的类属性,不会进 __init__ 的参数列表。

记住一句话:在 dataclass 里,name: str 是「字段声明」,name = '默认值' 是「类属性」,两者作用截然不同。

python
from dataclasses import dataclass


@dataclass
class Demo:
    a: int        # 这是字段,会进 __init__
    b: int = 10   # 这是有默认值的字段,也会进 __init__
    c = 20        # 注意:这里没有类型注解,被当成普通类属性,不会进 __init__


d = Demo(1)
print(d)
print(d.c)

输出:

Demo(a=1, b=10)
20

看到没?c 没出现在 repr 里,因为它根本不是一个字段,只是个挂在类上的常量。

比较两个 dataclass 对象

dataclass 默认会生成 __eq__,所以两个字段值完全相同的对象,会被判为相等:

python
from dataclasses import dataclass


@dataclass
class Employee:
    name: str
    dept: str
    salary: int


a = Employee('两点水', '研发部', 12000)
b = Employee('两点水', '研发部', 12000)
c = Employee('两点水', '研发部', 15000)

print(a == b)
print(a == c)
print(a is b)

输出:

True
False
False

注意第三行——a is b 是 False。== 比的是「字段值是否相等」,is 比的是「是不是同一个对象」。这两个事完全两码事,别混。

练习 1 / 4·用 @dataclass 定义 Point题目有问题?

请用 @dataclass 装饰器定义一个 Point 类:

  • 两个字段:x: int、y: int
  • 然后实例化 Point(1, 2) 赋给变量 p,并 print(p)

输出应该是:

Point(x=1, y=2)

(这正是 dataclass 自动生成的 __repr__ 输出。)

main.py
可编辑
🔒做对当前题解锁下一段 ·0/4
本章目录

dataclass

  1. 教学 01第二十节:dataclass
  2. 教学 02一、第一个 dataclass
  3. 练习 1用 @dataclass 定义 Point
  4. 教学 03二、默认值与 field()
  5. 练习 2 🔒可变默认值:default_factory
  6. 教学 04三、不可变 dataclass——frozen=True
  7. 练习 3 🔒frozen=True:不可变 + 可 hash
  8. 教学 05四、自动 __eq__ 与 __post_init__
  9. 练习 4 🔒自动 __eq__:按字段值比较
← 上一章19 · 异常处理
dataclass
下一章 →21 · 上下文管理器(with)