>>> from env_helper import info; info()
页面更新时间: 2023-12-27 10:44:58
运行环境:
    Linux发行版本: Debian GNU/Linux 12 (bookworm)
    操作系统内核: Linux-6.1.0-16-amd64-x86_64-with-glibc2.36
    Python版本: 3.11.2

6.11. 熟悉Python的迭代器协议

其实对于大部分Python程序员而言,迭代器的概念可能并不熟悉。但这很正常,与C++等语言不同,Python的迭代器集成在语言之中,与语言完美地无缝集成,不像C++中那 样需要专门去理解这一个概念。比如,要遍历一个容器,Python代码如下:

>>> alist = range(2)
>>> for i in alist:
>>>     print(i)
0
1

而C++代码如下:

using namespace std; vector<int> myIntVector; //往容器mylntVector中添加元素的操作,略 for(vector<int>::iterator - myIntVector.begin(); myIntVectorlterator != myIntVector.end(); myIntVectorIterator++)

{ cout<<*myIntVectorIterator<<"";}

两相对比,可以看到C++的代码中,多了 一个vector:: iterator类型,它是什么、 有什么用、什么时候用、怎么用,都是C++程序员需要理解和掌握的内容,所以可以说, 在“实现遍历容器”这一事情匕使用C++要付出更多的精力去学习更多的内容,这就是 Python把迭代器内建在语言之中的好处。

但是,并非所有的时候都能够隐藏细节,特别是在写一本书向读者讲述其中的机理的时 候。所以在这里,首先需要向大家介绍一下iter()函数iter()可以输人两个实参,但为了简 化起见,在这里忽略第二个可选参数,只介绍一个参数的形式。iter()函数返冋一个迭代器 对象,接受的参数是一个实现了__iter__()方法的容器或迭代器(精确来说,还支持仅有__getitem__()方法的容器)。对于容器而言,__iter__()方法返回一个迭代器对象.而对迭代器 而言,它的__iter__()方法返回其自身,所以如果我们用一个迭代器对象it,当以它为参数调 用iter(it)时,返回的是自身。

>>> it = iter(alist)
>>> it2 = iter(it)
>>> assert id(it) == id(it2)

到时此,就可以跟大家讲一下迭代器协议了。前文已经说过,所谓协议t是一种松散的 约定,并没有相应的接口定义,所以把协议简单归纳如下:

1)实现__iter__()方法,返回一个迭代器。

2)实现next()方法,返回当前的元素,并指向下一个元素的位置,如果当前位置已无元素.则抛出StopItcration异常。

可以通过以下代码验证这个协议:

>>> alist = range(2)
>>> it = alist.__iter__()
>>> next(it)
0
>>> next(it)
1
>>> next(it)
---------------------------------------------------------------------------

StopIteration                             Traceback (most recent call last)

<ipython-input-8-bc1ab118995a> in <module>()
----> 1 next(it)


StopIteration:

与上例使用iter()内置函数不同,这次的代码是it=alist,__iter__(),可见list这一容器 确实是实现了迭代器协议中容器的部分。后续连续3次的next()方法调用也印证了协议的 第2条。

熟悉了迭代器协议,那么我们就可以使用它来遍历所有的容器,仍然以list对象为例。

>>> alist = range(2)
>>> it = iter(alist)
>>> while True:
>>>     try:
>>>         print(next(it))
>>>     except StopIteration:
>>>         break
0
1

可以看到输出跟最初使用for循环是一样的,对,你的灵光一闪没有错,其实for语句 就是对获取容器的迭代器、调用迭代器的next()方法以及对Stoplteration进行处理等流程进 行封装的语法糖(类似的语法糖还有in/not it语句)。

迭代器最大的好处是定义了统一的访问容器(或集合)的统一接口,所以程序员可以随 时定义自己的迭代器,只要实现了迭代器协议就可以。除此之外,迭代器还有惰性求值的特 性,它仅可以在迭代至当前元素时才计算(或读取)该元素的值,在此之前可以不存在,在 此之后可以销毁,也就是说不需要在遍历之前事先准备好整个迭代过程中的所有元素,所以 非常适合遍历无穷个元素的集合(如斐波那契数列)或巨大的事物(如文件)。

class Fib(object):
def __init__(self):

self._a = 0 self._b = 1

def __iter__(self):

return self

def next(self):

self._a,self._b = self._b ,self._a + self._b return self._a

for i,f in enumerate(Fib()):

print(f) if i >10:

break

这段代码能够打印斐波那契数列的前10项。再来看一下传统的使用容器存储整个数列 的方案。

>>> def fib(n):
>>>     """返回小于指定的斐波那契数列"""
>>>     result = []
>>>     a,b =0,1
>>>     while b<n:
>>>         result.append(b)
>>>         a,b = b ,a+b
>>>     return result
>>> fib(10)
[1, 1, 2, 3, 5, 8]

与直接使用容器的代码相比,它仅使用两个成员变蛋,显而易见更省内存,并在一些 应用场景更省CPU汁算资源.所以在编写代码中应当多多使用迭代器协议,避免劣化代码。 对于这一观点,不必怀疑,从Python 2.3版本开始,itertools成为了标准库的一员已经充分印 证这个观点。

itertools的目标是提供一系列计算快速、内存高效的函数,这些函数可以单独使用,也 可以进行组合,这个模块受到了 Haskell等函数式编程语言的启发,所以大量使用itertools 模块中的函数的代码,看起来有点像函数式编程语言写推荐,比如sum(knap(opcrator.mul, vector1,veetor2)>能够用来运行两个向量的对应兀素乘积之和。

itertools最为人所熟知的版本,应该算是zip、map、filter、slice的替代,izip(izip_longest)、imap(startmap)、ifilter(iftlterfalse)、islice,它们与原来的那几个内置函数有一样的 功能,只是返回的是迭代器(在Python3中,新的E0数撤底替换掉了旧函数)。

除了对标准函数的替代,itertods还提供以下几个有用的函数:chain()用以同时连续地 迭代多个序列;compress()、dropwhile()和takewhile()能用以遴选序列元素;tee()就像同名 的UNIX应用程序,对序列作n次迭代;而groupby的效果类似SQL中相同拼写的关键字所 带的效果。

[k for k, g in groupby ( ' AAAR.BBBCCDAABBB' ) ] —> A B C D A B [list(q) for k, g in groupby('AAAABBBCCD')] --> AAAA BBB CC D

除了这些针对有限元素的迭代帮助函数之外,还有count()、cycle()、repeat()等函数产 生无穷序列,这3个函数就分别可以产生算术递增数列、无限重复实参序列的序列和重复产 生同一个值的序列。

如果以上这些函数让你感到吃惊,那么接下来的4个组合数学的函数就会让你更加惊讶了,如表6-3所示。

_images/img7.png

下面通过例子来熟悉一下,第一行是代码,第二行是结果。

>>> import itertools
>>> product('ABCD',repeat = 2)
<itertools.product at 0x7f3040297d38>
>>> itertools.permutations('ABCD',2)
<itertools.permutations at 0x7f30402bb2b0>
>>> itertools.combinations('ABCD',2)
<itertools.combinations at 0x7f30402ba138>
>>> itertools.combinations_with_replacement('ABCD',2)
<itertools.combinations_with_replacement at 0x7f30402ba098>
>>> for i in product('ABC','123',repeat=2):print(''.join(i))
A1A1
A1A2
A1A3
A1B1
A1B2
A1B3
A1C1
A1C2
A1C3
A2A1
A2A2
A2A3
A2B1
A2B2
A2B3
A2C1
A2C2
A2C3
A3A1
A3A2
A3A3
A3B1
A3B2
A3B3
A3C1
A3C2
A3C3
B1A1
B1A2
B1A3
B1B1
B1B2
B1B3
B1C1
B1C2
B1C3
B2A1
B2A2
B2A3
B2B1
B2B2
B2B3
B2C1
B2C2
B2C3
B3A1
B3A2
B3A3
B3B1
B3B2
B3B3
B3C1
B3C2
B3C3
C1A1
C1A2
C1A3
C1B1
C1B2
C1B3
C1C1
C1C2
C1C3
C2A1
C2A2
C2A3
C2B1
C2B2
C2B3
C2C1
C2C2
C2C3
C3A1
C3A2
C3A3
C3B1
C3B2
C3B3
C3C1
C3C2
C3C3