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

你有没有想过,为什么 for x in my_list 天然就能用?为什么 len() 对字符串、列表、字典都能工作? 这背后就是 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 Deck:
"""一副扑克牌 —— 只需 __len__ 和 __getitem__"""
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):
return len(self._cards)

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

# 两个方法,解锁一系列操作
deck = Deck()
print(len(deck)) # 52
print(deck[0]) # ('2', '♠') —— 索引
print(deck[-1]) # ('A', '♣') —— 负索引
print(deck[:5]) # 前5张牌 —— 切片
for card in deck: # for 循环
pass
print(('2', '♠') in deck) # True —— in 运算符
print(list(reversed(deck))[:3]) # 反向 —— reversed()

自动支持的操作清单

实现 __len____getitem__ 后,Python 自动给你:

操作方法示例
len()__len__len(deck)
索引__getitem__deck[0]
切片__getitem__deck[:5]
迭代__getitem__for card in deck
in 运算符__contains__ 或迭代('A','♠') in deck
reversed()__reversed____len__+__getitem__reversed(deck)
随机选择__len__random.choice(deck)
排序__getitem__+__len__sorted(deck)

排序的坑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
deck = Deck()

# 排序会报错,因为元组比较规则不适合牌面
# sorted(deck) # TypeError: '<' not supported

# 自定义排序键
def card_key(card):
rank_order = {r: i for i, r in enumerate(Deck.ranks)}
suit_order = {s: i for i, s in enumerate(Deck.suits)}
return (rank_order[card[0]], suit_order[card[1]])

sorted_cards = sorted(deck, key=card_key)
print(sorted_cards[0]) # ('2', '♠')
print(sorted_cards[-1]) # ('A', '♣')

🦆 鸭子类型:不问出身,只看能力

核心思想

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 不关注类型,只关注行为
def print_length(obj):
print(len(obj)) # 任何有 __len__ 的对象都可以

print_length([1, 2, 3]) # 3
print_length("hello") # 5
print_length({'a': 1, 'b': 2}) # 2
# print_length(42) # TypeError: 没有 __len__

# 另一个例子:任何可迭代的都能用 for
def print_items(iterable):
for item in iterable:
print(item)

print_items([1, 2, 3]) # 列表
print_items("abc") # 字符串
print_items(range(3)) # range
print_items({'a': 1}) # 字典(遍历键)

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
# 只要实现了 __iter__ 和 __next__,就是迭代器
class Fibonacci:
"""斐波那契数列迭代器"""
def __init__(self, max_count):
self.max_count = max_count
self.count = 0
self.a, self.b = 0, 1

def __iter__(self):
return self

def __next__(self):
if self.count >= self.max_count:
raise StopIteration
self.a, self.b = self.b, self.a + self.b
self.count += 1
return self.a

# 用起来和内置迭代器一样
fib = Fibonacci(10)
print(list(fib)) # [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

# 支持 for 循环、sum、max 等
fib = Fibonacci(10)
print(sum(fib)) # 143

鸭子类型 vs 类型检查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# ❌ 过度类型检查(Java 思维)
def process(data):
if isinstance(data, list):
return [x * 2 for x in data]
elif isinstance(data, tuple):
return tuple(x * 2 for x in data)
elif isinstance(data, str):
return data * 2
# ... 类型越多越难维护

# ✅ 鸭子类型(Python 思维)
def process(data):
"""任何可迭代的都行"""
return type(data)(x * 2 for x in data)

print(process([1, 2, 3])) # [2, 4, 6]
print(process((1, 2, 3))) # (2, 4, 6)
print(process("abc")) # aabbcc

🔧 进阶:让序列支持完整操作

添加切片和修改支持

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

class Vector:
"""自定义向量,支持完整序列操作"""

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

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

def __getitem__(self, index):
cls = type(self)
if isinstance(index, slice):
return cls(self._components[index])
elif isinstance(index, numbers.Integral):
return self._components[index]
else:
raise TypeError(f'索引类型错误: {type(index)}')

def __setitem__(self, index, value):
self._components[index] = value

def __repr__(self):
return f'Vector({self._components})'

def __eq__(self, other):
return len(self) == len(other) and all(a == b for a, b in zip(self, other))

def __abs__(self):
return sum(x * x for x in self) ** 0.5

def __add__(self, other):
return Vector(a + b for a, b in zip(self, other))

v = Vector([1, 2, 3, 4, 5])
print(v[0]) # 1
print(v[1:3]) # Vector([2, 3])
print(len(v)) # 5
print(abs(v)) # 7.416...
v[0] = 10
print(v) # Vector([10, 2, 3, 4, 5])

继承 collections.abc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from collections.abc import Sequence

class MyList(Sequence):
"""继承 Sequence,自动获得很多方法"""

def __init__(self, data):
self._data = list(data)

def __getitem__(self, index):
return self._data[index]

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

# Sequence 自动提供:index, count, __contains__, __iter__,
# __reversed__, __eq__, __ne__, __lt__, __le__, __gt__, __ge__

ml = MyList([3, 1, 4, 1, 5])
print(ml.index(4)) # 2 —— 自动获得
print(ml.count(1)) # 2 —— 自动获得
print(4 in ml) # True —— 自动获得
print(list(reversed(ml))) # [5, 1, 4, 1, 3]

🎯 本讲总结

自定义序列:只需实现 __len____getitem__,就能解锁索引、切片、迭代、排序等一整套功能。

鸭子类型:不关注类型,只关注行为。让你的代码更灵活、更 Pythonic。

进阶用法:通过继承 collections.abc.Sequence 自动获得更多方法,省去自己实现的麻烦。

切片处理__getitem__ 中需要区分整数索引和切片,分别返回元素和新序列对象。


📚 推荐教材

《Python 编程从入门到实践(第 3 版)》 | 《流畅的 Python(第 2 版)》 | 《CPython 设计与实现》

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


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

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

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

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

🔗 课程导航

上一讲:对象引用与可变性 | 下一讲:继承与组合


💬 联系我

平台账号/链接
微信扫码加好友
B 站Python 自动化办公社区

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

🎓 AI 编程实战课程

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

fluent-python.png