本文中所涉及的代码,在未特殊声明的情况下,都是基于Python3程序设计语言编写的。
建议您在PC浏览器中阅读本文,以获得更好的阅读体验。
0
迭代器(iterator)
通过一致的方式遍历“序列”,是Python的一个重要特性。这个特性是通过迭代器协议来实现的。迭代器协议是一种令对象可迭代遍历的通用方式。
其实你已经很熟悉“序列”遍历的方式,例如for循环。我们来举个例子:图1
在图1的代码中,vec并不一个迭代器对象(iterator object)。在运行for循环时,如果in后面的对象不是迭代器对象,Python解析器会尝试帮你创建一个,如果创建成功,才会继续执行循环操作。
所以图1的实际效果,是这样子的:图2
iter用于生成一个迭代器对象。当然了,在很多接受迭代器对象的上下文中(如for循环,内置函数min、max、sum、list的构造函数等),你并不需要调用iter来生成一个迭代器对象,Python会帮你做。
1
生成器(generator)
为了方便表述,我们将“迭代器对象”,以及在可用于接受“迭代器对象”上下文中的对象(如list、dict、tuple、str等)称为“可迭代对象”(iteratable obejct)。Python文档中貌似没有这个说法,但为了更好的理解生成器,大家就先按这个思路来。
生成器是构造可迭代对象的一种方式。定义一个生成器非常简单,它的语法跟定义普通函数一样,只需要将返回值的return语句改成yield即可。注意,yield只能用于生成器中。
生成器是生成器对象(generator object)的构造函数,为了方便表述,我们将直接称之为生成器函数。也就是说,你每次调用生成器“函数”,它并不会执行“函数”里边的代码,而是返回一个生成器对象。图3
如图3代码所示,gen就是一个生成器,而go则是生成器对象。生成器对象在被使用的时候,会执行生成器函数里边的代码,直到遇到yield语句,yield返回结果,并暂停代码的执行;直到对象下一次被使用,再往下执行。在for循环中,go会被使用3次,yield被遇到了三次,之后,gen里边的while循环结束,函数返回,向外部的for抛出一个StopIteration异常,外部for结束。
2
next与__next__
迭代器和生成器的共同特点是实现了__next__和__iter__成员函数。其中,__iter__使得它们都可用于可迭代上下文中;而__next__使得它们可以通过next函数来访问。
在生成器中,__next__执行了生成器构造函数的代码,直至遇到yield语句,返回yield提供的值,并暂停代码的执行。在可迭代上下文中,生成器对象的__next__函数会变不停地调用,直至生成器遇到return语句,或者执行完毕(即收到StopIteration异常)。
而内置函数next就是用于隐式地调用__next__;当然,你也可以直接显式地调用__next__。
注意观察下面的代码,观察”hello“、”ready“、”done“的来源和打印时机。图4
我们注意到,在手动调用next来遍历go2的例子中,第4次调用next时,抛出了一个StopIteraton异常。这是因为go2的for循环只执行了1次,加上”ready“和”done“,共3次yield,所以我们只能通过next取到3次正常的yield返回值。
在一般的迭代器对象上下文中(如for循环),Python会帮我们处理StopIteration异常。
3
send成员函数
生成器向我们提供了send函数,用于向生成yield提供一个返回值,以便控制生成器代码的行为。其实语法如以下代码所示:
val = yield ret
由于在遇到yield时,生成器的代码会被暂停,所以在生成器代码开始执行时,利用send向yield提供返回值并没有意义;实际上,如果你这么做,系统会抛出一个异常。如果你想用send来开始执行生成器的代码,可以将None作为参数传给send。
为了更好的理解send,我们来看如下的代码:图5
实际上,__next__()相当于send(None)。图6中的代码,使用了图5的gen生成了一个go,留意print(x)的结果都是打印None。图6
关于如何利用send来控制生成器的行为,我们可以来看如图7的例子:图7
4
生成器中的return语句
当生成器的代码执行完毕,或者遇到return语句时,生成器抛出一个StopIteration异常。事实上,在生成器中,return语句相当于raise StopIteration()。
在Python3.3之前的版本中,如果你在return语句之后提供一个或多个值,会被视为语法错误。而在新的版本中,return v则相当于raise StopIteration(v)。
注意观察图8所示的运行结果。无论是用return语句,还是用raise语句,生成器结束的时候,都是抛出StopIteration("Peace")。图8
5
生成器表达式
用生成器表达式来创建生成器对象更为简单。生成器表达式的语法和列表、字典等的推导式很像。创建一个生成器表达式,只需要将列表推导式的中括号替换成小括号即可。
在图9的例子中,我们将分别使用生成器函数和生成器表达式来定义两个效果一样的生成器对象。显然,生成器表达的代码更为简洁;但返过来,利用生成器函数可以做更加复杂的事情。图9
在生成器表达式中,可能嵌套多个for循环,最左边的for循环的元素最先被计算,优先级也最高,右边的for可以使用左边的for的元素。我们以三个for循环的生成器表达式为例子,来充分展示多个for循环之间的关系。图10
在图10的例子中,第2个for循环使用了最左边for循环的元素;最右侧的for循环遍历的次数最多,为4次;而最左侧的for循环只遍历了1次。