本文参考《流畅的 Python》(Fluent Python)一书,深入讲解 Python 的数据模型(Data Model),帮助你理解 Python 中"魔法方法"的本质与威力。

什么是数据模型?

Python 的数据模型(Data Model)是语言的核心设计之一。它定义了对象如何与语言的各种特性交互——比如迭代、比较、运算符重载、属性访问等。这些交互通过特殊方法(Special Methods,俗称"魔法方法"或"dunder methods",即双下划线方法)来实现。

核心思想:Python 的数据模型让自定义对象能够像内置类型一样自然地工作。当你实现了这些特殊方法,你的对象就获得了与内置类型相同的能力。

特殊方法的本质

特殊方法以双下划线开头和结尾,例如 __init____len____getitem__ 等。它们不是为直接调用而设计的,而是被 Python 解释器在特定场景下自动调用。

1
2
3
4
5
# 不要这样做
obj.__len__()

# 应该这样做
len(obj) # Python 会自动调用 obj.__len__()

这种设计体现了 Python 的一致性:内置类型和用户自定义类型使用相同的接口。

六大核心类别

1. 对象初始化与销毁

方法触发时机说明
__new__(cls, ...)创建实例时控制实例创建,很少需要重写
__init__(self, ...)实例初始化时最常见的初始化方法
__del__(self)实例被垃圾回收时析构方法,慎用
1
2
3
4
5
6
7
8
9
10
11
12
class Vector:
def __new__(cls, *components):
# 控制实例创建
print(f"Creating instance of {cls.__name__}")
return super().__new__(cls)

def __init__(self, *components):
self._components = components

def __del__(self):
# 析构方法,不建议依赖它做关键清理
print(f"Destroying {self}")

2. 字符串表示

方法触发方式用途
__repr__(self)repr(obj)开发者调试,应尽可能精确
__str__(self)str(obj) / print()面向用户的友好表示
__format__(self, fmt)format(obj, fmt)格式化输出
__bytes__(self)bytes(obj)字节表示
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y

def __repr__(self):
# 精确、无歧义的表示
return f"Vector({self.x!r}, {self.y!r})"

def __str__(self):
# 友好的用户表示
return f"({self.x}, {self.y})"

def __format__(self, fmt):
# 自定义格式化
if fmt == "polar":
import math
r = math.hypot(self.x, self.y)
theta = math.atan2(self.y, self.x)
return f"(r={r:.2f}, θ={math.degrees(theta):.1f}°)"
return str(self)

v = Vector(3, 4)
print(repr(v)) # Vector(3, 4)
print(str(v)) # (3, 4)
print(f"{v:polar}") # (r=5.00, θ=53.1°)

3. 集合类协议(容器与序列)

这是 Python 数据模型中最强大的部分之一。实现这些方法,你的对象就能像 listdictset 一样工作。

方法触发方式说明
__len__(self)len(obj)返回元素数量
__getitem__(self, key)obj[key]按索引或键获取
__setitem__(self, key, value)obj[key] = value设置元素
__delitem__(self, key)del obj[key]删除元素
__contains__(self, item)item in obj成员检查
__iter__(self)iter(obj) / for迭代器
__reversed__(self)reversed(obj)反向迭代
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
from typing import Iterator

class Vector:
def __init__(self, *components):
self._components = components

def __len__(self) -> int:
return len(self._components)

def __getitem__(self, index):
# 支持切片:v[1:3]
if isinstance(index, slice):
return Vector(*self._components[index])
return self._components[index]

def __iter__(self) -> Iterator[float]:
return iter(self._components)

def __contains__(self, item) -> bool:
return item in self._components

def __reversed__(self):
return Vector(*reversed(self._components))

# 现在 Vector 可以像列表一样使用了!
v = Vector(1.0, 2.0, 3.0, 4.0)
print(len(v)) # 4
print(v[1]) # 2.0
print(v[1:3]) # Vector(2.0, 3.0)
print(2.0 in v) # True
print(list(reversed(v))) # [4.0, 3.0, 2.0, 1.0]

4. 数值类型协议

让你的对象支持数学运算。

方法触发方式说明
__add__, __radd__a + b加法
__sub__, __rsub__a - b减法
__mul__, __rmul__a * b乘法
__truediv__a / b真除法
__floordiv__a // b地板除
__mod__a % b取模
__pow__a ** b幂运算
__neg__-a取负
__abs__abs(a)绝对值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y

def __add__(self, other):
if isinstance(other, Vector):
return Vector(self.x + other.x, self.y + other.y)
return NotImplemented # 让 Python 尝试 other.__radd__

def __radd__(self, other):
return self.__add__(other)

def __mul__(self, scalar):
if isinstance(scalar, (int, float)):
return Vector(self.x * scalar, self.y * scalar)
return NotImplemented

def __rmul__(self, scalar):
return self.__mul__(scalar)

def __neg__(self):
return Vector(-self.x, -self.y)

def __abs__(self):
import math
return math.hypot(self.x, self.y)

def __repr__(self):
return f"Vector({self.x}, {self.y})"

v1 = Vector(1, 2)
v2 = Vector(3, 4)

print(v1 + v2) # Vector(4, 6)
print(v1 * 3) # Vector(3, 6)
print(3 * v1) # Vector(3, 6) ← __rmul__
print(-v1) # Vector(-1, -2)
print(abs(v1)) # 2.236...

5. 比较与哈希

方法触发方式说明
__eq__a == b相等比较
__ne__a != b不等比较
__lt__a < b小于
__le__a <= b小于等于
__gt__a > b大于
__ge__a >= b大于等于
__hash__hash(obj)哈希值,用于 dict/set
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
from functools import total_ordering

@total_ordering # 自动推导其他比较方法
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y

def __eq__(self, other):
if not isinstance(other, Vector):
return NotImplemented
return (self.x, self.y) == (other.x, other.y)

def __lt__(self, other):
if not isinstance(other, Vector):
return NotImplemented
# 按模长比较
return abs(self) < abs(other)

def __hash__(self):
# 必须基于不可变属性
return hash((self.x, self.y))

def __abs__(self):
import math
return math.hypot(self.x, self.y)

v1 = Vector(1, 2)
v2 = Vector(3, 4)
v3 = Vector(1, 2)

print(v1 == v3) # True
print(v1 < v2) # True
print(v1 <= v3) # True(由 @total_ordering 推导)

# 可以作为 dict 的 key
mapping = {v1: "point A"}
print(mapping[v3]) # "point A"

6. 上下文管理器协议

方法触发方式说明
__enter__with 语句进入时返回上下文对象
__exit__with 语句退出时处理异常清理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from typing import Optional
import time

class Timer:
"""计时器上下文管理器"""

def __enter__(self):
self.start = time.perf_counter()
return self

def __exit__(self, exc_type, exc_val, exc_tb) -> Optional[bool]:
self.end = time.perf_counter()
self.elapsed = self.end - self.start
print(f"耗时: {self.elapsed:.4f} 秒")
# 不处理异常,返回 None
return None

# 使用
with Timer() as t:
time.sleep(0.1)
# 输出: 耗时: 0.1001 秒

可调用对象

通过 __call__ 方法,让对象可以像函数一样被调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from typing import Callable

class Average:
"""计算移动平均"""

def __init__(self):
self._items = []

def __call__(self, value: float) -> float:
self._items.append(value)
return sum(self._items) / len(self._items)

def __repr__(self):
return f"Average({self._items!r})"

avg = Average()
print(avg(10)) # 10.0
print(avg(20)) # 15.0
print(avg(30)) # 20.0

属性描述符(进阶)

描述符是 Python 数据模型中更高级的特性,用于控制属性访问。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
from typing import Any

class Validator:
"""验证属性值的描述符"""

def __init__(self, min_value: float, max_value: float):
self.min_value = min_value
self.max_value = max_value
self.name = ""

def __set_name__(self, owner, name):
self.name = name

def __get__(self, instance, owner):
if instance is None:
return self
return instance.__dict__[self.name]

def __set__(self, instance, value: Any):
if not self.min_value <= value <= self.max_value:
raise ValueError(
f"{self.name} 必须在 [{self.min_value}, {self.max_value}] 之间,"
f"得到 {value}"
)
instance.__dict__[self.name] = value

class Person:
age = Validator(0, 150)
score = Validator(0, 100)

def __init__(self, age: int, score: float):
self.age = age
self.score = score

p = Person(25, 88.5)
p.age = 30
# p.age = 200 # ValueError: age 必须在 [0, 150] 之间,得到 200

数据模型的设计哲学

《流畅的 Python》一书强调,Python 数据模型的核心设计哲学是:

  1. 协议驱动:Python 不强制继承,而是通过实现协议(一组特殊方法)来获得能力。这被称为"鸭子类型"的极致体现。

  2. 一致性:内置类型和用户自定义类型使用相同的接口。len()liststr 和你自己的类都有效。

  3. 最小惊讶原则:实现特殊方法后,对象的行为应该符合直觉。+ 应该表示"合并"或"相加",而不是其他含义。

  4. 组合优于继承:通过实现协议,你可以让类获得多种能力,而不需要复杂的继承层次。

完整示例:自定义序列类型

下面是一个完整的自定义序列类型,展示了数据模型的实际应用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
from typing import Iterator, Iterable, TypeVar, Generic

T = TypeVar("T")

class SortedList(Generic[T]):
"""保持排序的列表"""

def __init__(self, initial: Iterable[T] = ()):
self._items: list[T] = sorted(initial)

def __len__(self) -> int:
return len(self._items)

def __getitem__(self, index: int | slice) -> T | "SortedList[T]":
if isinstance(index, slice):
return SortedList(self._items[index])
return self._items[index]

def __setitem__(self, index: int, value: T) -> None:
# 保持排序:先替换,再排序
self._items[index] = value
self._items.sort()

def __delitem__(self, index: int) -> None:
del self._items[index]

def __iter__(self) -> Iterator[T]:
return iter(self._items)

def __reversed__(self) -> Iterator[T]:
return reversed(self._items)

def __contains__(self, item: T) -> bool:
# 利用有序特性,使用二分查找
import bisect
index = bisect.bisect_left(self._items, item)
return index != len(self._items) and self._items[index] == item

def __repr__(self) -> str:
return f"SortedList({self._items!r})"

def add(self, item: T) -> None:
"""添加元素,保持有序"""
import bisect
bisect.insort(self._items, item)

def index(self, item: T) -> int:
"""查找元素位置"""
import bisect
index = bisect.bisect_left(self._items, item)
if index != len(self._items) and self._items[index] == item:
return index
raise ValueError(f"{item!r} not in list")

# 使用示例
sl = SortedList([3, 1, 4, 1, 5, 9, 2, 6])
print(sl) # SortedList([1, 1, 2, 3, 4, 5, 6, 9])
print(len(sl)) # 8
print(sl[3]) # 3
print(5 in sl) # True
print(list(sl)) # [1, 1, 2, 3, 4, 5, 6, 9]

sl.add(7)
print(sl) # SortedList([1, 1, 2, 3, 4, 5, 6, 7, 9])

总结

Python 的数据模型是理解这门语言的关键。通过掌握特殊方法,你可以:

  • 让自定义对象像内置类型一样工作
  • 写出更 Pythonic、更直观的代码
  • 利用协议而非继承来扩展功能
  • 创建与其他 Python 代码无缝集成的库

《流畅的 Python》一书将这些概念称为"Python 风格的对象"。当你真正理解了数据模型,你就能写出既优雅又强大的 Python 代码。

参考