01-本章概述
约 1829 字大约 6 分钟
2026-09-28
Python 允许我们定义自己的类,但“能够创建对象”只是第一步。真正值得关注的问题是:怎样让自己定义的对象表现得像 Python 内置对象一样自然?
例如,我们可能希望一个自定义对象能够直接使用这些写法:
len(obj) # 获取对象包含的元素数量
obj[index] # 使用下标读取元素
for item in obj: # 迭代对象
item in obj # 判断对象是否包含某个元素
left + right # 对两个对象执行加法
bool(obj) # 判断对象的真假
print(obj) # 输出适合阅读的对象信息这些操作并不是只为 Python 内置类型准备的。自定义类型只要实现相应的对象协议,也能接入相同的语法、内置函数和标准库工具。
1. Python 数据模型是什么
这里所说的“数据模型”(Data Model)不是数据库领域中的数据建模,也不是单纯描述一个类有哪些字段。
Python 数据模型规定了 Python 对象如何参与语言中的各种操作。例如:
- 对象如何被创建和初始化;
- 对象如何转换成字符串;
- 对象如何支持长度、索引、切片和迭代;
- 对象如何参与算术运算和比较;
- 对象如何进行真假判断;
- 对象如何管理属性和上下文。
可以把它理解为 Python 解释器与对象之间的一组约定。对象按照这些约定实现相应方法后,解释器就知道如何对它执行通用操作。
2. 特殊方法是对象协议的入口
Python 使用一组名称前后带双下划线的方法表示这些约定,例如:
__len__
__getitem__
__repr__
__bool__
__add__这类方法通常称为特殊方法(special method),也经常被称为双下方法(dunder method)。其中 dunder 是 “double underscore” 的缩写。
特殊方法通常不是业务代码直接调用的普通 API。我们更常使用 Python 提供的语法或内置函数,由解释器在背后寻找并调用对应的特殊方法。
例如:
len(obj)对于自定义类型,这个表达式通常会触发类型中定义的 __len__ 方法。类似地:
len(obj) -> __len__
obj[index] -> __getitem__
repr(obj) -> __repr__
bool(obj) -> __bool__,必要时回退到 __len__
left + right -> __add__,必要时尝试反向运算协议后续小节会分别展开这些调用过程、返回值要求和回退规则。
3. 本章的两个核心案例
本章使用两个逐步扩展的案例说明 Python 数据模型。
一副 Pythonic 的纸牌
第一个案例是一个表示整副扑克牌的 FrenchDeck 类。
这个类最初只实现少量特殊方法,但它的对象可以像标准序列一样使用:
- 通过
len(deck)获取纸牌数量; - 通过
deck[0]或deck[-1]读取纸牌; - 使用切片一次读取多张纸牌;
- 使用
for正向遍历; - 使用
reversed反向遍历; - 使用
in判断某张牌是否存在; - 交给
random.choice随机抽牌; - 交给
sorted按自定义规则排序。
这个案例的重点不是扑克牌本身,而是展示协议带来的组合能力:类只实现少数基础操作,Python 的语法、内置函数和标准库就能在此基础上提供更多行为。
一个二维向量
第二个案例是表示二维向量的 Vector 类。
通过逐步实现特殊方法,它将支持:
- 使用
repr(vector)获得明确的对象表示; - 使用
abs(vector)计算向量的模; - 使用
bool(vector)判断向量是否为零向量; - 使用
vector1 + vector2完成向量加法; - 使用
vector * scalar完成标量乘法。
这个案例用于说明,自定义对象可以接入 Python 的数值运算协议,并表现得像语言原生支持的数值类型。
4. 本章各小节介绍什么
接下来的内容按照原章节的主要结构展开。
一副 Pythonic 的纸牌
首先实现纸牌和整副牌的数据结构,然后讲解 __len__ 与 __getitem__。在此基础上,继续分析索引、切片、迭代、成员判断、随机选择和排序是如何工作的。
特殊方法是如何使用的
这一部分从具体案例转向运行机制,说明特殊方法为什么通常由解释器隐式调用,以及 Python 如何查找这些方法。这里还会讲解常见的协议回退规则,避免把 obj.__len__() 与 len(obj) 简单理解成完全相同的调用。
模拟数值类型
这一部分通过二维向量实现对象表示、绝对值、真假判断、向量加法和标量乘法,并讨论运算符方法应该返回新对象还是修改原对象,以及不支持某项运算时应该怎样处理。
字符串表示形式
这一部分区分 __repr__ 与 __str__,说明交互式控制台、调试器、日志、repr()、str() 和 print() 分别需要怎样的对象表示。
自定义类型的布尔值
这一部分说明 Python 如何判断自定义对象的真假,并展开 __bool__、__len__ 和默认真值之间的回退顺序。
容器 API
这一部分介绍 Python 容器协议的核心组成,包括可迭代、具有长度和支持成员判断等能力,并说明 Iterable、Sized、Container、Collection、Sequence、Mapping 与 Set 等抽象基类之间的关系。
特殊方法概述
这一部分按照对象表示、容器、迭代、数值运算、属性访问、上下文管理和对象创建等用途,对常见特殊方法进行分类。重点是建立查找方向,而不是背诵方法名称。
为什么 len 不是方法
最后讨论 Python 为什么采用 len(obj),而不是要求所有对象调用 obj.len()。这个问题涉及语言一致性、内置类型的实现效率,以及自定义对象如何通过 __len__ 接入统一协议。
5. 本章要建立的核心认识
学习本章时,最重要的不是记住全部特殊方法,而是建立以下认识:
- Python 的许多语法和内置函数都建立在对象协议之上;
- 特殊方法是对象参与这些协议的入口;
- 应优先使用公开语法和内置函数,而不是直接调用特殊方法;
- 实现少量基础协议,往往可以获得多项组合能力;
- 自定义类型应尽量遵守内置类型已经形成的行为习惯;
- 是否实现某个特殊方法,应由对象需要表达的语义决定。
后续小节将通过纸牌和向量案例,把这些认识落实到可以运行和验证的代码中。
拓展阅读与查阅
以下资料用于进一步研究或日后查阅。理解本节内容不依赖这些链接。
