一文了解Python的迭代器的实现
本文对迭代器的解释参考自:https://www.programiz.com/python-programming/iterator
最后自己使用迭代器实现一个公平洗牌类。
博主认为,理论来自实践,假若只学习理论而不实践,都是无用功。
Iterators in Python
迭代器在Python中无处不在。它们可以通过for循环优雅的使用。但是它的实现却被隐藏起来。
从技术上讲,Python迭代器对象必须实现两个特殊的方法,分别是__iter__() 和 __next__(),这两个方法也叫做Python的魔术方法,类似于一种迭代器协议。
如果我们可以将Python对象转换成一个迭代器,那么我们可以称这个对象是可以迭代的。像Python中的内置数据结构 list(列表)、tuple(元组)、string(字符串)等都是可迭代的。
注意:这里可迭代与迭代器是不同的概念,下面会讲到。
遍历迭代器
我们可以通过next()方法不断从迭代器中获取下一个元素。当迭代器中元素遍历完毕后,再次调用next()方法,迭代器会抛出StopIteration异常。下面是例子。
# :创建一个列表。 >>> test_list = [5, 4, 3, 2, 1, 0] # :使用iter()将列表转换成迭代器。 >>> test_iter = iter(test_list) # :使用next方法我们可以得到迭代器中的元素。 >>> print(next(test_iter)) 5 >>> print(next(test_iter)) 4 >>> print(next(test_iter)) 3 >>> print(next(test_iter)) 2 # :我们可以调用迭代器的魔法方法__next__获取下一个元素。 >>> print(test_iter.__next__()) 1 >>> next(test_iter) 0 # :当迭代器中元素遍历完毕,再调用next()时迭代器抛出错误。 >>> next(test_iter) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
注意,这里我们如果不进行iter()操作的话,列表是否还支持next()等操作?我们看下面实际操作。
>>> test_list = [5, 4, 3, 2, 1, 0] >>> next(test_list) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'list' object is not an iterator
没错,程序发生报错,并且报错原因就是list并不是一个迭代器。
我们前面也强调过可迭代与迭代器并不是同一个概念,我们可以说list是可迭代的,但并不能说它是迭代器。
读到这里大家可能会疑问,平常使用for循环便利list的时候也没有主动将其变成迭代器操作的,别急,我们接着看下面的。
用于迭代器的for循环
>>> test_list = [5, 4, 3, 2, 1, 0] >>> for i in test_list: ... print(i) ... 5 4 3 2 1 0
上面这个使用for循环遍历列表的例子也屡见不鲜了。实际上,for循环可以遍历任何可迭代对象。下面我们来看看for循环的实现。
iter_obj = iter(iterable) while True: try: element = next(iter_obj) except StopIteration: break
因此,for 循环在内部通过iter()方法产生一个迭代器对象。接着使用next()方法依次获取迭代器内部元素,直到抛出异常为止。
构建自定义的迭代器
前面我们也提到过对象中的__iter__()和__next__()方法。通过更改这两个魔法方法我们可以很轻易实现一个自定义的迭代器。
__iter__()返回一个迭代器对象,当然我们也可以在当中根据需要进行一些初始化操作。
__next__()返回下一项,此方法在调用到结尾时必须抛出StopIteration()。
下面这个例子实现要返回2的幂次方的迭代器对象。
class PowTwo: def __init__(self, max=0): self.max = max def __iter__(self): # :返回一个迭代器,可以是自己。 self.n = 0 return self def __next__(self): # :判断是否结束遍历。 if self.n <= self.max: result = 2 ** self.n self.n += 1 return result else: raise StopIteration numbers = PowTwo(3) # :获取迭代器。 i = iter(numbers) print(next(i)) print(next(i)) print(next(i)) print(next(i)) print(next(i))
# :输出
1 2 4 8 Traceback (most recent call last): File "/home/bsoyuj/Desktop/Untitled-1.py", line 32, in <module> print(next(i)) File "<string>", line 18, in __next__ raise StopIteration StopIteration
当然我们也可以使用for循环。
>>> for i in PowTwo(5): ... print(i) ... 1 2 4 8 16 32
注意
最后注意的是,迭代器要有尽头,类似于递归,迭代器也要有迭代结束条件来防止迭代器会无限迭代。
Knuth洗牌示例
这里结合Knuth洗牌算法实现一个洗牌类。此算法为知名的公平洗牌算法。此算法详细链接。
import random COLORS = ['红桃', '黑桃', '方片', '梅花'] class Knuth: """Kunuth洗牌算法""" def __init__(self): self._pokers = [] for color in COLORS: for i in range(1, 14): self._pokers.append((i, color)) self._pokers.append(('大王')) self._pokers.append(('小王')) # :记录扑克牌索引。 self._index = 0 # :一套扑克最多有54张牌。 self._max_index = 54 def __iter__(self): return self def __next__(self): if self._index < self._max_index: card = self._pokers[self._index] self._index += 1 return card else: raise StopIteration def shuffle_cards(self): """洗牌""" for i in range(53, 0, -1): swap_index = random.randint(0, i) self._pokers[i], self._pokers[swap_index] = self._pokers[swap_index], self._pokers[i] # :计数索引归零。 self._index = 0 a = Knuth() a.shuffle_cards() for i in a: print(i)
运行结果。
(2, '梅花') (13, '红桃') (3, '黑桃') (6, '方片') (5, '红桃') 大王 (3, '红桃') (8, '方片') (4, '黑桃') (9, '方片') (1, '红桃') (10, '红桃') (6, '梅花') (8, '梅花') ...