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

267 lines
8.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 第 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 MappingORM模型。
例如,`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` 的购物车计算与独立列表验证正确。