大家好,我是正在实战各种 AI 项目的程序员晚枫。

🎬 开篇:为什么你的代码"差点意思"?

你有没有遇到过这样的困惑?

明明功能都实现了,代码也能跑,但总觉得:

  • 代码写得很啰嗦,不如别人的简洁
  • 自定义类用起来"笨重",不像内置类型那么顺手
  • 面试被问到"实现一个支持加法的向量类",不知道从何下手

这不是你技术不行,而是你没掌握 Python 数据模型。

一个真实的对比

看看两种实现向量类的方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# ❌ 普通实现:啰嗦、不直观
class VectorV1:
def __init__(self, x, y):
self.x = x
self.y = y

def add(self, other):
return VectorV1(self.x + other.x, self.y + other.y)

def multiply(self, scalar):
return VectorV1(self.x * scalar, self.y * scalar)

def length(self):
return (self.x ** 2 + self.y ** 2) ** 0.5

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

# 使用时:啰嗦、不直观
v1 = VectorV1(3, 4)
v2 = VectorV1(1, 2)
v3 = v1.add(v2)
print(v3.to_string()) # Vector(4, 6)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# ✅ Pythonic 实现:优雅、直观
class VectorV2:
def __init__(self, x, y):
self.x = x
self.y = y

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

def __add__(self, other):
return VectorV2(self.x + other.x, self.y + other.y)

def __mul__(self, scalar):
return VectorV2(self.x * scalar, self.y * scalar)

def __abs__(self):
return (self.x ** 2 + self.y ** 2) ** 0.5

# 使用时:简洁、直观
v1 = VectorV2(3, 4)
v2 = VectorV2(1, 2)
print(v1 + v2) # Vector(4, 6) - 直接用加号
print(v1 * 3) # Vector(9, 12) - 直接用乘号
print(abs(v1)) # 5.0 - 直接用 abs

第二种方式,就是通过 Python 数据模型实现的。

想象一下,你设计了一个向量类 Vector。你希望它能像数字一样相加、相乘,能打印出友好的字符串表示,能用 len() 获取维度。这些功能不是 Python 自动给你的,而是通过实现特殊方法(也叫魔术方法)来实现的。


🎯 什么是 Python 数据模型?

核心思想:协议优于继承

Python 的数据模型是一套协议和约定,它定义了对象如何与 Python 的其他部分交互。

不同于 Java、C# 需要显式实现接口,Python 采用"鸭子类型":

如果它走起来像鸭子,叫起来像鸭子,那它就是鸭子。

只要你的对象实现了特定的方法,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
# 只要有 __len__,Python 就认为它有长度
class MyList:
def __len__(self):
return 42

obj = MyList()
print(len(obj)) # 42 - Python 自动调用 obj.__len__()

# 只要有 __iter__ 和 __next__,Python 就认为它可迭代
class MyRange:
def __init__(self, n):
self.n = n
self.i = 0

def __iter__(self):
return self

def __next__(self):
if self.i >= self.n:
raise StopIteration
value = self.i
self.i += 1
return value

for i in MyRange(3):
print(i) # 0, 1, 2

为什么叫"特殊方法"?

特殊方法都用双下划线开头和结尾,所以也叫 dunder methods(double underscore):

  • __init__ 读作 "dunder init"
  • __len__ 读作 "dunder len"

为什么用双下划线?
为了避免和用户定义的方法冲突。你不用担心自己的 len 方法和 Python 的 __len__ 冲突。

Python 如何调用特殊方法?

关键点: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
class Demo:
def __init__(self):
print("初始化...")

def __repr__(self):
print("转换为字符串...")
return "Demo()"

def __len__(self):
print("计算长度...")
return 42

def __getitem__(self, key):
print(f"获取元素: {key}")
return key * 2

# 创建对象 - 自动调用 __init__
d = Demo() # 输出: 初始化...

# 打印对象 - 自动调用 __repr__
print(d) # 输出: 转换字符串... Demo()

# 获取长度 - 自动调用 __len__
print(len(d)) # 输出: 计算长度... 42

# 索引访问 - 自动调用 __getitem__
print(d[5]) # 输出: 获取元素: 5 10

你永远不需要这样写:

1
2
3
4
5
6
7
8
9
# ❌ 不要这样写
print(d.__repr__())
print(d.__len__())
print(d.__getitem__(5))

# ✅ 应该这样写
print(repr(d))
print(len(d))
print(d[5])

📊 特殊方法速查表

创建与销毁

方法作用触发场景
__new__创建对象实例创建对象时(在 __init__ 之前)
__init__初始化对象创建对象时
__del__销毁对象垃圾回收时(不推荐依赖)

字符串表示

方法作用触发场景
__repr__官方字符串表示repr()、交互式解释器
__str__用户友好字符串str()print()
__format__格式化字符串format()、f-string
__bytes__字节序列bytes()

容器协议

方法作用触发场景
__len__长度len()、布尔判断
__getitem__获取元素obj[key]、切片、迭代
__setitem__设置元素obj[key] = value
__delitem__删除元素del obj[key]
__contains__成员检测value in obj
__iter__迭代器iter()for 循环
__reversed__反向迭代reversed()

数值运算

方法作用触发场景
__add__加法a + b
__sub__减法a - b
__mul__乘法a * b
__truediv__除法a / b
__floordiv__整除a // b
__mod__取模a % b
__pow__幂运算a ** b
__neg__负号-a
__abs__绝对值abs(a)

比较运算

方法作用触发场景
__eq__等于a == b
__ne__不等于a != b
__lt__小于a < b
__le__小于等于a <= b
__gt__大于a > b
__ge__大于等于a >= b

可调用对象

方法作用触发场景
__call__调用obj()

上下文管理

方法作用触发场景
__enter__进入上下文with obj as x:
__exit__退出上下文with 块结束时

属性访问

方法作用触发场景
__getattr__获取不存在的属性obj.attr(attr 不存在时)
__getattribute__获取任意属性obj.attr(任何情况)
__setattr__设置属性obj.attr = value
__delattr__删除属性del obj.attr

🔑 关键协议详解

1. 序列协议:让你的对象像列表一样工作

序列协议只需要实现 __len____getitem__ 两个方法:

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
class Deck:
"""一副扑克牌 - 完整的序列协议示例"""
ranks = [str(n) for n in range(2, 11)] + list('JQKA')
suits = '♠♥♦♣'

def __init__(self):
self._cards = [
(rank, suit) for suit in self.suits
for rank in self.ranks
]

def __len__(self):
"""支持 len() 函数"""
return len(self._cards)

def __getitem__(self, position):
"""支持索引和切片"""
return self._cards[position]

# 创建扑克牌
deck = Deck()

# 只实现两个方法,获得了这么多功能:
print(len(deck)) # 52 - len() 支持
print(deck[0]) # ('2', '♠') - 索引支持
print(deck[-1]) # ('A', '♣') - 负索引支持
print(deck[::13]) # 每13张取一张 - 切片支持
print(deck[0:5]) # 前5张 - 切片支持

# 迭代支持
for card in deck[:5]:
print(card)

# in 运算符支持
print(('A', '♠') in deck) # True

# random 模块支持
import random
print(random.choice(deck)) # 随机抽一张
print(random.sample(deck, 5)) # 随机抽5张

为什么只实现两个方法就能获得这么多功能?

因为 Python 会根据 __getitem__ 自动推断其他行为:

  • 切片:传入 slice 对象,__getitem__ 返回新序列
  • 迭代:从 0 开始不断调用 __getitem__,直到 IndexError
  • in:遍历比较

2. 数值协议:让你的对象像数字一样运算

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
65
66
67
68
69
70
71
72
73
import math

class Vector:
"""二维向量 - 完整的数值协议示例"""

def __init__(self, x=0, y=0):
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 __abs__(self):
"""向量长度:abs(v)"""
return math.hypot(self.x, self.y)

def __bool__(self):
"""布尔值:零向量为 False"""
return bool(abs(self))

def __add__(self, other):
"""向量加法:v1 + v2"""
return Vector(self.x + other.x, self.y + other.y)

def __sub__(self, other):
"""向量减法:v1 - v2"""
return Vector(self.x - other.x, self.y - other.y)

def __mul__(self, scalar):
"""标量乘法:v * 3"""
return Vector(self.x * scalar, self.y * scalar)

def __rmul__(self, scalar):
"""反向乘法:3 * v"""
return self.__mul__(scalar)

def __truediv__(self, scalar):
"""标量除法:v / 3"""
return Vector(self.x / scalar, self.y / scalar)

def __neg__(self):
"""负向量:-v"""
return Vector(-self.x, -self.y)

def __eq__(self, other):
"""相等比较:v1 == v2"""
return self.x == other.x and self.y == other.y

# 使用示例
v1 = Vector(3, 4)
v2 = Vector(1, 2)

print(v1) # (3, 4) - 调用 __str__
print(repr(v1)) # Vector(3, 4) - 调用 __repr__
print(abs(v1)) # 5.0 - 调用 __abs__
print(bool(v1)) # True - 调用 __bool__
print(v1 + v2) # (4, 6) - 调用 __add__
print(v1 - v2) # (2, 2) - 调用 __sub__
print(v1 * 3) # (9, 12) - 调用 __mul__
print(3 * v1) # (9, 12) - 调用 __rmul__
print(v1 / 2) # (1.5, 2.0) - 调用 __truediv__
print(-v1) # (-3, -4) - 调用 __neg__
print(v1 == v2) # False - 调用 __eq__

# 链式运算
v3 = Vector(1, 0)
v4 = Vector(0, 1)
print(v1 + v2 * 2 - v3) # 支持混合运算

__rmul__ 是什么?

3 * v1 执行时:

  1. Python 先尝试 3.__mul__(v1) - int 不知道如何处理 Vector
  2. 失败后,尝试 v1.__rmul__(3) - Vector 的反向乘法

这就是 Python 的运算符机制:先左后右

3. 上下文管理协议:让你的对象支持 with 语句

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
65
66
67
68
class FileManager:
"""文件管理器 - 自动处理文件的打开和关闭"""

def __init__(self, filename, mode='r'):
self.filename = filename
self.mode = mode
self.file = None

def __enter__(self):
"""进入上下文:打开文件"""
print(f"打开文件: {self.filename}")
self.file = open(self.filename, self.mode)
return self.file # 返回文件对象

def __exit__(self, exc_type, exc_val, exc_tb):
"""退出上下文:关闭文件"""
print(f"关闭文件: {self.filename}")
if self.file:
self.file.close()
# 返回 False 表示不抑制异常
# 返回 True 表示抑制异常
return False

# 使用示例
with FileManager('test.txt', 'w') as f:
f.write('Hello, Python!')
# 自动关闭文件,即使发生异常

# 带异常处理的版本
class SafeDBConnection:
"""安全的数据库连接"""

def __init__(self, connection_string):
self.connection_string = connection_string
self.conn = None

def __enter__(self):
self.conn = self._connect()
return self.conn

def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is not None:
# 发生异常,回滚事务
print(f"发生异常: {exc_val},执行回滚")
self.conn.rollback()
else:
# 正常退出,提交事务
print("正常提交")
self.conn.commit()
self.conn.close()
return False # 不抑制异常

def _connect(self):
# 模拟数据库连接
class MockConnection:
def commit(self): pass
def rollback(self): pass
def close(self): pass
return MockConnection()

# 使用示例
with SafeDBConnection('mysql://...') as conn:
# 执行数据库操作
pass # 正常提交

with SafeDBConnection('mysql://...') as conn:
raise ValueError("出错了")
# 自动回滚

4. 可迭代协议:让你的对象支持 for 循环

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
# 方式1:实现 __iter__ 返回迭代器
class MyRange:
"""自定义范围 - 实现 __iter__"""

def __init__(self, start, end, step=1):
self.start = start
self.end = end
self.step = step

def __iter__(self):
"""返回一个迭代器"""
return MyRangeIterator(self.start, self.end, self.step)

class MyRangeIterator:
"""迭代器实现"""

def __init__(self, start, end, step):
self.current = start
self.end = end
self.step = step

def __iter__(self):
"""迭代器自身也是可迭代的"""
return self

def __next__(self):
"""返回下一个元素"""
if self.current >= self.end:
raise StopIteration
value = self.current
self.current += self.step
return value

# 使用
for i in MyRange(1, 10, 2):
print(i) # 1, 3, 5, 7, 9

# 方式2:使用生成器更简洁
class MyRangeV2:
"""使用生成器实现 - 更简洁"""

def __init__(self, start, end, step=1):
self.start = start
self.end = end
self.step = step

def __iter__(self):
"""生成器函数自动返回迭代器"""
current = self.start
while current < self.end:
yield current
current += self.step

# 使用
for i in MyRangeV2(1, 10, 2):
print(i) # 1, 3, 5, 7, 9

📊 性能对比

__len__ 的性能

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
import time

class SlowLen:
"""低效的 __len__ 实现"""
def __init__(self, data):
self.data = data

def __len__(self):
# 每次都遍历计算
count = 0
for _ in self.data:
count += 1
return count

class FastLen:
"""高效的 __len__ 实现"""
def __init__(self, data):
self.data = data

def __len__(self):
# 直接使用内置函数
return len(self.data)

# 性能测试
data = list(range(1000000))

slow = SlowLen(data)
fast = FastLen(data)

# 测试慢版本
start = time.time()
for _ in range(1000):
len(slow)
print(f"SlowLen: {time.time() - start:.4f}s") # 约 0.5s

# 测试快版本
start = time.time()
for _ in range(1000):
len(fast)
print(f"FastLen: {time.time() - start:.4f}s") # 约 0.0001s

结论__len__ 会被频繁调用,务必优化实现。

__getitem__ vs 直接访问

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
import timeit

class DirectAccess:
"""直接访问内部列表"""
def __init__(self, data):
self.data = data

class GetItemAccess:
"""通过 __getitem__ 访问"""
def __init__(self, data):
self.data = data

def __getitem__(self, key):
return self.data[key]

data = list(range(10000))
direct = DirectAccess(data)
getitem = GetItemAccess(data)

# 性能测试
direct_time = timeit.timeit(
lambda: direct.data[5000],
number=1000000
)

getitem_time = timeit.timeit(
lambda: getitem[5000],
number=1000000
)

print(f"直接访问: {direct_time:.4f}s")
print(f"__getitem__: {getitem_time:.4f}s")
print(f"开销: {(getitem_time - direct_time) / direct_time * 100:.1f}%")

结论__getitem__ 有轻微开销,但换来了巨大的灵活性。


🚀 进阶用法

1. __repr__ vs __str__ 深度解析

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
class Person:
"""演示 __repr__ 和 __str__ 的区别"""

def __init__(self, name, age, email):
self.name = name
self.age = age
self.email = email

def __repr__(self):
"""
官方字符串表示
目标:无歧义,能用来重建对象
场景:调试、日志、交互式解释器
"""
return f"Person(name='{self.name}', age={self.age}, email='{self.email}')"

def __str__(self):
"""
用户友好字符串
目标:易读,给终端用户看
场景:print()、str()、用户界面
"""
return f"{self.name}{self.age}岁)"

def __format__(self, format_spec):
"""
格式化字符串
支持自定义格式规范
"""
if format_spec == 'short':
return f"{self.name}({self.age})"
elif format_spec == 'full':
return f"{self.name}, {self.age}岁, {self.email}"
else:
return str(self)

# 使用示例
p = Person("张三", 30, "zhangsan@example.com")

print(repr(p)) # Person(name='张三', age=30, email='zhangsan@example.com')
print(str(p)) # 张三(30岁)
print(p) # 张三(30岁)- print 调用 __str__

# 交互式解释器显示 repr
>>> p
Person(name='张三', age=30, email='zhangsan@example.com')

# 容器中显示 repr
print([p, p]) # [Person(name='张三', age=30, ...), ...]

# 自定义格式
print(f"{p:short}") # 张三(30)
print(f"{p:full}") # 张三, 30岁, zhangsan@example.com

最佳实践

  • 总是实现 __repr__,它是最重要的调试工具
  • __repr__ 应该能 eval() 重建对象(如果可能)
  • __str__ 是可选的,默认会 fallback 到 __repr__

2. 实现 __bool__ 让对象更智能

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
class Collection:
"""集合类 - 支持布尔判断"""

def __init__(self, items=None):
self.items = items if items is not None else []

def __len__(self):
return len(self.items)

def __bool__(self):
"""
自定义布尔值
默认:空容器为 False,非空为 True
"""
return len(self) > 0

# 使用
empty = Collection()
full = Collection([1, 2, 3])

if empty:
print("有元素")
else:
print("空集合") # 输出这个

if full:
print("有元素") # 输出这个

# 更实用的例子:结果对象
class Result:
"""操作结果"""

def __init__(self, success, data=None, error=None):
self.success = success
self.data = data
self.error = error

def __bool__(self):
return self.success

def __repr__(self):
if self.success:
return f"Result(data={self.data})"
else:
return f"Result(error='{self.error}')"

# 使用
def divide(a, b):
if b == 0:
return Result(False, error="除数不能为0")
return Result(True, data=a / b)

result = divide(10, 2)
if result:
print(f"结果: {result.data}") # 结果: 5.0

result = divide(10, 0)
if not result:
print(f"错误: {result.error}") # 错误: 除数不能为0

3. __getitem__ 支持多维索引

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
class Matrix:
"""二维矩阵 - 支持多维索引"""

def __init__(self, rows, cols):
self.rows = rows
self.cols = cols
self.data = [[0] * cols for _ in range(rows)]

def __repr__(self):
return f"Matrix({self.rows}x{self.cols})"

def __getitem__(self, key):
"""
支持多种索引方式:
- matrix[i, j]:获取元素
- matrix[i]:获取整行
- matrix[i:j, k:l]:获取子矩阵
"""
if isinstance(key, tuple):
# 多维索引:matrix[i, j]
row, col = key
return self.data[row][col]
elif isinstance(key, slice):
# 行切片:matrix[i:j]
return [row[:] for row in self.data[key]]
else:
# 单行索引:matrix[i]
return self.data[key]

def __setitem__(self, key, value):
if isinstance(key, tuple):
row, col = key
self.data[row][col] = value
else:
self.data[key] = value

# 使用示例
m = Matrix(3, 3)

# 设置值
m[0, 0] = 1
m[1, 1] = 2
m[2, 2] = 3

# 获取值
print(m[0, 0]) # 1
print(m[1]) # [0, 2, 0] - 整行
print(m[0:2]) # 前两行

4. 实现 __hash__ 支持作为字典键

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 Point:
"""二维点 - 可哈希"""

def __init__(self, x, y):
self.x = x
self.y = y

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

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

def __hash__(self):
"""让对象可哈希,能作为字典键或存入集合"""
return hash((self.x, self.y))

# 使用
p1 = Point(1, 2)
p2 = Point(1, 2)
p3 = Point(3, 4)

print(p1 == p2) # True
print(p1 is p2) # False

# 作为字典键
distances = {
Point(0, 0): 0,
Point(1, 0): 1,
Point(0, 1): 1,
}

print(distances[Point(1, 0)]) # 1 - 不同对象但相等

# 存入集合
points = {p1, p2, p3}
print(len(points)) # 2 - p1 和 p2 相等,只存一个

⚠️ 避坑指南

陷阱 1:可变默认参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# ❌ 错误示例 - 默认参数在定义时就创建了
class BadExample:
def __init__(self, items=[]):
self.items = items

a = BadExample()
a.items.append(1)
print(a.items) # [1]

b = BadExample()
print(b.items) # [1] - 共享了同一个列表!

# ✅ 正确做法
class GoodExample:
def __init__(self, items=None):
self.items = items if items is not None else []

a = GoodExample()
a.items.append(1)
print(a.items) # [1]

b = GoodExample()
print(b.items) # [] - 独立的列表

陷阱 2:忘记调用父类 __init__

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Parent:
def __init__(self):
self.parent_value = 100

# ❌ 错误示例
class BadChild(Parent):
def __init__(self, value):
self.value = value
# 忘记调用 super().__init__()

c = BadChild(1)
print(c.value) # 1
print(c.parent_value) # AttributeError!

# ✅ 正确做法
class GoodChild(Parent):
def __init__(self, value):
super().__init__() # 先初始化父类
self.value = value

c = GoodChild(1)
print(c.value) # 1
print(c.parent_value) # 100

陷阱 3:__eq__ 破坏了 __hash__

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
# ❌ 错误示例 - 定义 __eq__ 后,默认 __hash__ 被禁用
class BadPoint:
def __init__(self, x, y):
self.x = x
self.y = y

def __eq__(self, other):
return self.x == other.x and self.y == other.y

p = BadPoint(1, 2)
# hash(p) # TypeError: unhashable type

# ✅ 正确做法 - 同时实现 __hash__
class GoodPoint:
def __init__(self, x, y):
self.x = x
self.y = y

def __eq__(self, other):
return self.x == other.x and self.y == other.y

def __hash__(self):
return hash((self.x, self.y))

p = GoodPoint(1, 2)
print(hash(p)) # 正常工作

规则:如果实现了 __eq__,要么同时实现 __hash__,要么显式设置 __hash__ = None

陷阱 4:可变对象的 __hash__

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
# ❌ 危险示例 - 可变对象不应该可哈希
class MutablePoint:
def __init__(self, x, y):
self.x = x
self.y = y

def __eq__(self, other):
return self.x == other.x and self.y == other.y

def __hash__(self):
return hash((self.x, self.y))

p = MutablePoint(1, 2)
points = {p}

p.x = 3 # 修改对象
# p 现在在错误的位置!字典可能找不到它

# ✅ 正确做法 - 可变对象不可哈希
class SafeMutablePoint:
__hash__ = None # 显式禁用哈希

def __init__(self, x, y):
self.x = x
self.y = y

def __eq__(self, other):
return self.x == other.x and self.y == other.y

# hash(SafeMutablePoint(1, 2)) # TypeError

陷阱 5:__getattr__ vs __getattribute__

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
# __getattr__:只在属性不存在时调用
class Demo1:
def __init__(self):
self.x = 1

def __getattr__(self, name):
print(f"获取不存在的属性: {name}")
return None

d1 = Demo1()
print(d1.x) # 1 - 存在,不调用 __getattr__
print(d1.y) # 获取不存在的属性: y -> None

# __getattribute__:每次获取属性都调用
class Demo2:
def __init__(self):
self.x = 1

def __getattribute__(self, name):
print(f"获取属性: {name}")
# 必须用 object.__getattribute__ 避免无限递归
return object.__getattribute__(self, name)

d2 = Demo2()
print(d2.x) # 获取属性: x -> 1
print(d2.y) # 获取属性: y -> AttributeError

# 风险:__getattribute__ 容易写出无限递归
class BadDemo:
def __init__(self):
self.x = 1

def __getattribute__(self, name):
# ❌ 错误:这会造成无限递归
# return self.__dict__[name]

# ✅ 正确:用 object 的方法
return object.__getattribute__(self, name)

🎯 实战案例:实现一个完整的扑克牌类

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
import random
from collections import namedtuple

# 使用命名元组表示卡牌
Card = namedtuple('Card', ['rank', 'suit'])

class FrenchDeck:
"""法国扑克牌 - 52 张"""
ranks = [str(n) for n in range(2, 11)] + list('JQKA')
suits = '♠♥♦♣'

def __init__(self):
self._cards = [
Card(rank, suit)
for suit in self.suits
for rank in self.ranks
]

def __len__(self):
return len(self._cards)

def __getitem__(self, position):
return self._cards[position]

def __repr__(self):
return f"FrenchDeck({len(self)} cards)"

def __bool__(self):
return len(self) > 0

def shuffle(self):
"""洗牌"""
random.shuffle(self._cards)
return self

def deal(self, n=1):
"""发牌"""
if n > len(self):
raise ValueError("牌不够了")
hand = [self._cards.pop() for _ in range(n)]
return hand

def sort_by_suit(self):
"""按花色排序"""
self._cards.sort(key=lambda c: self.suits.index(c.suit))
return self

def sort_by_rank(self):
"""按点数排序"""
self._cards.sort(key=lambda c: self.ranks.index(c.rank))
return self

# 使用示例
deck = FrenchDeck()
print(deck) # FrenchDeck(52 cards)
print(len(deck)) # 52
print(deck[0]) # Card(rank='2', suit='♠')
print(deck[-1]) # Card(rank='A', suit='♣')

# 洗牌
deck.shuffle()
print(deck[:5]) # 前5张(随机)

# 发牌
hand = deck.deal(5)
print(f"你拿到了: {hand}")
print(f"剩余: {len(deck)} 张")

# 排序
deck.sort_by_rank()
print(deck[:5]) # 前5张(点数最小)

# 遍历所有 A
for card in deck:
if card.rank == 'A':
print(card)

# 统计花色
from collections import Counter
suits = Counter(card.suit for card in deck)
print(suits) # Counter({'♠': 13, '♥': 13, '♦': 13, '♣': 13})

🎯 本讲总结

通过本讲,我们理解了:

知识点核心要点
数据模型Python 对象交互的协议和约定,"协议优于继承"
特殊方法双下划线方法,Python 在特定时机自动调用
序列协议__len__ + __getitem__,获得切片、迭代等功能
数值协议__add____mul__ 等,让对象像数字一样运算
上下文管理__enter__ + __exit__,支持 with 语句
字符串表示__repr__ 调试用,__str__ 用户看
性能考虑__len__ 会被频繁调用,务必优化
常见陷阱可变默认参数、忘记 super()、破坏 hash

记住这句话

Python 数据模型让你写的类能无缝融入 Python 的语法体系,用最少的代码实现最多的功能。


📚 推荐教材

《Python 编程从入门到实践(第 3 版)》 - Eric Matthes 著

Python 零基础入门首选。本书分为基础语法和项目实战两部分,适合完全没有编程经验的读者。

《流畅的 Python(第 2 版)》 - Luciano Ramalho 著

Python 进阶经典之作。深入讲解 Python 的高级特性,包括数据模型、函数式编程、面向对象、元编程等。

《CPython 设计与实现》 - Anthony Shaw 著

本书深入讲解 CPython 内部机制,从内存管理到字节码执行,从对象模型到并发编程。配合本课程学习,效果更佳。

学习路线建议:

1
零基础 → 《从入门到实践》 → 《流畅的 Python》 → 本门课程 → 《CPython 设计与实现》

🎓 加入《流畅的 Python》直播共读营

学到这里,如果你想系统吃透这本书——欢迎加入我的直播共读课。

  • 每周直播精讲,逐章拆解核心知识点
  • 专属学习群,随时答疑交流
  • 试运营特惠:499 元299 元

👉 【立即报名《流畅的 Python》共读课】https://mp.weixin.qq.com/s/ivHJwn1nNx5ug4TFrapvGg


🔗 课程导航

课程大纲 | 下一讲:数据容器深度解析


💬 联系我

平台账号/链接
微信扫码加好友
微博@程序员晚枫
知乎@程序员晚枫
抖音@程序员晚枫
小红书@程序员晚枫
B 站Python 自动化办公社区

主营业务:AI 编程培训、企业内训、技术咨询

🎓 AI 编程实战课程

想系统学习 AI 编程?程序员晚枫的 AI 编程实战课 帮你从零上手!

fluent-python.png