本文对迭代器的解释参考自: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"", line 1, in StopIteration
注意,这里我们如果不进行iter()操作的话,列表是否还支持next()等操作?我们看下面实际操作。
>>> test_list = [5, 4, 3, 2, 1, 0]>>>next(test_list)
Traceback (most recent call last):
File"", line 1, in TypeError:'list' object is not an iterator
没错,程序发生报错,并且报错原因就是list并不是一个迭代器。
我们前面也强调过可迭代与迭代器并不是同一个概念,我们可以说list是可迭代的,但并不能说它是迭代器。
读到这里大家可能会疑问,平常使用for循环便利list的时候也没有主动将其变成迭代器操作的,别急,我们接着看下面的。
用于迭代器的for循环
>>> test_list = [5, 4, 3, 2, 1, 0]>>> for i intest_list:
...print(i)
...5
4
3
2
10
上面这个使用for循环遍历列表的例子也屡见不鲜了。实际上,for循环可以遍历任何可迭代对象。下面我们来看看for循环的实现。
iter_obj =iter(iterable)whileTrue:try:
element=next(iter_obj)exceptStopIteration:break
因此,for 循环在内部通过iter()方法产生一个迭代器对象。接着使用next()方法依次获取迭代器内部元素,直到抛出异常为止。
构建自定义的迭代器
前面我们也提到过对象中的__iter__()和__next__()方法。通过更改这两个魔法方法我们可以很轻易实现一个自定义的迭代器。
__iter__()返回一个迭代器对象,当然我们也可以在当中根据需要进行一些初始化操作。
__next__()返回下一项,此方法在调用到结尾时必须抛出StopIteration()。
下面这个例子实现要返回2的幂次方的迭代器对象。
classPowTwo:def __init__(self, max=0):
self.max=maxdef __iter__(self):#:返回一个迭代器,可以是自己。
self.n =0returnselfdef __next__(self):#:判断是否结束遍历。
if self.n <=self.max:
result= 2 **self.n
self.n+= 1
returnresultelse:raiseStopIteration
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
print(next(i))
File "", line 18, in __next__
raise StopIteration
StopIteration
当然我们也可以使用for循环。
>>> for i in PowTwo(5):
...print(i)
...1
2
4
8
16
32
注意
最后注意的是,迭代器要有尽头,类似于递归,迭代器也要有迭代结束条件来防止迭代器会无限迭代。
Knuth洗牌示例
这里结合Knuth洗牌算法实现一个洗牌类。此算法为知名的公平洗牌算法。此算法详细链接。
importrandom
COLORS= ['红桃', '黑桃', '方片', '梅花']classKnuth:"""Kunuth洗牌算法"""
def __init__(self):
self._pokers=[]for color inCOLORS: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):returnselfdef __next__(self):if self._index
card=self._pokers[self._index]
self._index+= 1
returncardelse:raiseStopIterationdefshuffle_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 ina:print(i)
运行结果。
(2, '梅花')
(13, '红桃')
(3, '黑桃')
(6, '方片')
(5, '红桃')
大王
(3, '红桃')
(8, '方片')
(4, '黑桃')
(9, '方片')
(1, '红桃')
(10, '红桃')
(6, '梅花')
(8, '梅花')
...