Files
PythonLearn/03_面向对象/3_4_Python特殊方法与数据类/README.md

8.1 KiB
Raw Blame History

第 3-4 课Python 特殊方法与数据类

一、本课定位

Java 业务对象中经常需要构造器、toString()equals()hashCode(),项目也常借助 record 或 Lombok 减少样板代码。Python 通过特殊方法和数据类Data Class@dataclass)解决类似问题。

本课只选择业务开发中最常见的内容,不展开运算符重载的全部细节。

二、本课目标

完成本课后,你将能够:

  1. 理解 Python 特殊方法的调用机制;
  2. 区分 __str____repr__
  3. 理解 __eq__ 如何改变对象相等比较;
  4. 使用 @dataclass 减少数据对象的样板代码;
  5. 使用 __post_init__ 执行业务校验;
  6. 使用 field(default_factory=list) 避免共享可变默认值;
  7. 理解 dataclass 与 Java POJO、record、Lombok 的边界差异。

三、差异速查表

需求 Java 常见方式 Python 常见方式
用户友好显示 toString() __str__
开发调试表示 通常仍使用 toString() __repr__
内容相等 重写 equals() 重写 __eq__
减少数据类样板代码 record 或 Lombok @dataclass
构造后校验 构造器代码块 __post_init__
每个对象独立列表 构造时 new ArrayList<>() field(default_factory=list)

四、特殊方法是什么

名称前后都有双下划线的方法称为特殊方法Special Method也常被称为双下方法Dunder Method

通常不是直接调用:

item.__str__()

而是使用正常语法,让 Python 自动调用:

str(item)
print(item)
item_one == item_two

这与 Java 运行时在字符串拼接或打印对象时调用 toString() 的体验相似。

五、strrepr

__str__ 面向使用者,强调友好、易读:

def __str__(self) -> str:
    return f"{self.name}{self.price} 元"

__repr__ 面向开发和调试,通常包含类名和明确字段:

Product(name='机械键盘', price=500.0)

调用关系:

  • str(obj)print(obj) 优先使用 __str__
  • repr(obj)、交互环境和容器显示通常使用 __repr__
  • 如果没有定义 __str__Python 可能退回使用 __repr__

六、eq 与对象相等

普通类如果没有实现 __eq__,两个字段内容相同但身份不同的对象通常不相等:

first = Product("键盘", 500.0)
second = Product("键盘", 500.0)

实现 __eq__ 后,可以按照业务字段比较。@dataclass 默认会根据声明的全部字段自动生成 __eq__

注意:相等比较与对象身份判断不同:

  • first == second 调用相等逻辑;
  • first is second 判断是否为同一个对象。

七、@dataclass 生成什么

from dataclasses import dataclass


@dataclass
class Product:
    name: str
    price: float

默认会自动生成常用方法,包括:

  • __init__:接收字段并初始化;
  • __repr__:生成适合调试的表示;
  • __eq__:按照字段值比较。

因此一般不要再手写完全相同的样板方法。

八、dataclass 不是什么

@dataclass 并不会:

  • 自动进行运行时类型检查;
  • 自动完成业务规则校验;
  • 默认创建不可变对象;
  • 自动等同于 Java record
  • 自动替代数据库对象关系映射Object Relational MappingORM模型。

例如,price: float 只是类型提示,不能自动阻止负数或字符串。

九、使用 post_init 校验

dataclass 自动生成 __init__ 后会调用 __post_init__

@dataclass
class Product:
    name: str
    price: float

    def __post_init__(self) -> None:
        if self.price < 0:
            raise ValueError("价格不能小于 0。")

它适合执行字段间校验、格式整理或需要在初始化完成后执行的逻辑。

十、可变默认值与 default_factory

列表是可变对象。下面这种字段定义不应使用:

items: list[Product] = []

dataclass 会直接拒绝常见的可变默认值,避免多个实例意外共享同一列表。

正确写法:

from dataclasses import dataclass, field


@dataclass
class Cart:
    items: list[Product] = field(default_factory=list)

default_factory=list 表示每次创建 Cart 时调用 list(),得到新的独立列表。

十一、与 Java record 和 Lombok 的区别

可以建立近似理解,但不要认为它们完全相同:

  • dataclass 默认可变Java record 的组件引用不可重新赋值;
  • dataclass 可以写普通方法、继承和自定义特殊方法;
  • dataclass 是 Python 标准库功能,不需要额外依赖或编译期注解处理;
  • @dataclass(frozen=True) 可以限制字段重新赋值,但仍不是安全边界,也不是深层不可变。

本阶段先掌握默认 dataclassfrozen=True 按业务需要再使用。

十二、什么时候使用 dataclass

适合:

  • 主要职责是保存数据;
  • 需要清晰的字段定义;
  • 希望自动获得初始化、调试显示和内容比较;
  • 对象仍然包含少量与数据紧密相关的行为。

不应仅因为“这是一个类”就添加 dataclass。复杂服务对象、资源管理器或主要依靠行为工作的对象通常使用普通类更自然。

十三、完整示例与运行方法

示例文件:

03_面向对象/3_4_Python特殊方法与数据类/special_methods_dataclass_example.py

在项目根目录运行:

python .\03_面向对象\3_4_Python特殊方法与数据类\special_methods_dataclass_example.py

示例会展示普通类与 dataclass 的代码差异、字符串显示、字段相等、初始化校验和独立列表。

十四、常见错误

14.1 把 repr 只当作另一个 str

二者用途不同:一个偏向开发调试,一个偏向使用者阅读。

14.2 认为类型注解会自动校验

price: float 不会自动拒绝字符串或负数。外部数据与业务规则仍需显式校验。

14.3 手写 dataclass 已生成的方法

如果自动行为已经符合需求,不要重复编写 __init____repr____eq__

14.4 共享可变默认值

列表、字典和集合等可变默认值应使用 default_factory 创建。

14.5 默认认为 dataclass 可以哈希

可变 dataclass 通常不会自动提供可用的 __hash__。是否可作为字典键或集合元素涉及可变性和相等契约,本课不强行展开。

十五、课堂练习

打开:

03_面向对象/3_4_Python特殊方法与数据类/practice.py

完成订单项和购物车:

  1. 使用 dataclass 定义字段;
  2. 使用 __post_init__ 校验单价和数量;
  3. 使用 property 计算小计;
  4. 使用 __str__ 提供友好显示;
  5. 验证自动生成的 __repr____eq__
  6. 使用 default_factory=list 创建独立购物车列表。

十六、参考答案与验证标准

参考答案暂不写入练习文件。完成后按以下层级验证:

  • 必须修复:语法或运行错误、计算错误、校验缺失、共享列表、手写并破坏 dataclass 自动行为;
  • 建议改进:重复计算、职责或命名明显不清晰;
  • 可选优化:非必要类型注解、输出美化和不影响行为的格式问题。

只有“必须修复”会阻止课程验收。

十七、本课小结

  • 特殊方法让对象参与 Python 的标准语法;
  • __str__ 面向使用者,__repr__ 面向开发调试;
  • __eq__ 决定 == 的相等逻辑;
  • @dataclass 自动生成初始化、调试表示和字段比较;
  • dataclass 类型注解不会自动执行运行时校验;
  • __post_init__ 适合完成初始化后的业务校验;
  • default_factory 为每个实例创建独立的可变字段。

十八、验收标准

  • 能正确使用 @dataclass
  • 能解释 __str____repr__ 的区别;
  • 能观察自动生成的 __eq__ 行为;
  • 能使用 __post_init__ 阻止非法数据;
  • 能使用 field(default_factory=list)
  • 能说明 dataclass 与 Java record 并非完全等价;
  • practice.py 的购物车计算与独立列表验证正确。