# 第 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)。 通常不是直接调用: ```python item.__str__() ``` 而是使用正常语法,让 Python 自动调用: ```python str(item) print(item) item_one == item_two ``` 这与 Java 运行时在字符串拼接或打印对象时调用 `toString()` 的体验相似。 ## 五、__str__ 与 __repr__ `__str__` 面向使用者,强调友好、易读: ```python def __str__(self) -> str: return f"{self.name}:{self.price} 元" ``` `__repr__` 面向开发和调试,通常包含类名和明确字段: ```text Product(name='机械键盘', price=500.0) ``` 调用关系: - `str(obj)` 和 `print(obj)` 优先使用 `__str__`; - `repr(obj)`、交互环境和容器显示通常使用 `__repr__`; - 如果没有定义 `__str__`,Python 可能退回使用 `__repr__`。 ## 六、__eq__ 与对象相等 普通类如果没有实现 `__eq__`,两个字段内容相同但身份不同的对象通常不相等: ```python first = Product("键盘", 500.0) second = Product("键盘", 500.0) ``` 实现 `__eq__` 后,可以按照业务字段比较。`@dataclass` 默认会根据声明的全部字段自动生成 `__eq__`。 注意:相等比较与对象身份判断不同: - `first == second` 调用相等逻辑; - `first is second` 判断是否为同一个对象。 ## 七、@dataclass 生成什么 ```python from dataclasses import dataclass @dataclass class Product: name: str price: float ``` 默认会自动生成常用方法,包括: - `__init__`:接收字段并初始化; - `__repr__`:生成适合调试的表示; - `__eq__`:按照字段值比较。 因此一般不要再手写完全相同的样板方法。 ## 八、dataclass 不是什么 `@dataclass` 并不会: - 自动进行运行时类型检查; - 自动完成业务规则校验; - 默认创建不可变对象; - 自动等同于 Java record; - 自动替代数据库对象关系映射(Object Relational Mapping,ORM)模型。 例如,`price: float` 只是类型提示,不能自动阻止负数或字符串。 ## 九、使用 __post_init__ 校验 dataclass 自动生成 `__init__` 后会调用 `__post_init__`: ```python @dataclass class Product: name: str price: float def __post_init__(self) -> None: if self.price < 0: raise ValueError("价格不能小于 0。") ``` 它适合执行字段间校验、格式整理或需要在初始化完成后执行的逻辑。 ## 十、可变默认值与 default_factory 列表是可变对象。下面这种字段定义不应使用: ```python items: list[Product] = [] ``` dataclass 会直接拒绝常见的可变默认值,避免多个实例意外共享同一列表。 正确写法: ```python 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)` 可以限制字段重新赋值,但仍不是安全边界,也不是深层不可变。 本阶段先掌握默认 dataclass,`frozen=True` 按业务需要再使用。 ## 十二、什么时候使用 dataclass 适合: - 主要职责是保存数据; - 需要清晰的字段定义; - 希望自动获得初始化、调试显示和内容比较; - 对象仍然包含少量与数据紧密相关的行为。 不应仅因为“这是一个类”就添加 dataclass。复杂服务对象、资源管理器或主要依靠行为工作的对象通常使用普通类更自然。 ## 十三、完整示例与运行方法 示例文件: ```text 03_面向对象/3_4_Python特殊方法与数据类/special_methods_dataclass_example.py ``` 在项目根目录运行: ```powershell 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__`。是否可作为字典键或集合元素涉及可变性和相等契约,本课不强行展开。 ## 十五、课堂练习 打开: ```text 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` 的购物车计算与独立列表验证正确。