生成器最典型的例子就是创建列表:list=[ i+3 for i in range(100)] . " i+3 for i in range(100)" 就是一个生成器。生成器返回了一个迭代器。看另外一个例子:
def gensample():
return (i+3 for i in range(10))
print(gensample())
<generator object gensample.<locals>.<genexpr> at 0x000001EDA91DE6D8>
Python在处理函数中的yield语句时,返回yield语句所指定的对象/值,但不会终止当前函数的执行,而是暂时中断,保留当前的执行状态/上下文,等函数再次被调用时则接着上次yield语句继续执行,如遇到yield则再次中断并保留当前的执行状态/上下文,如此循环直到函数结束或遇到return语句时才产生一个StopIteration异常后退出。 因此包含yield语句的函数的返回值不再是普通函数返回值,而是一个生成器。
使用yield语句创建的生成器函数:
def letter_range(a,z):
while ord(a)<ord(z):
yield a
a=chr(ord(a)+1)
c1=letter_range('a','z')
c2=letter_range('a','z')
print("c1="+str(c1))
print("c2="+str(c2))
c1=<generator object letter_range at 0x00000173D41CC048>
c2=<generator object letter_range at 0x00000173D41CC0C0>
上面例子的输出可以看出每次直接调用letter_range函数都会返回一个独立的生成器。
f=letter_range('a','z')
print(f.__next__())
print(f.__next__())
a
b
上面代码中,通过把生成器函数赋值给f,然后通过引用f来调用生成器就可以实现迭代了。
下面的代码在yield语句后面加了一个return语句,然后我们再调用生成器,结果能清晰的反映包含yield语句函数的执行路径。
def letter_range(a,z):
while ord(a)<ord(z):
yield a
return 'over'
a=chr(ord(a)+1)
f=letter_range('a','z')
print(f.__next__())
print(f.__next__())
a
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-102-33175d5529f1> in <module>
1 f=letter_range('a','z')
2 print(f.__next__())
----> 3 print(f.__next__())
StopIteration: over
下面的例子可以看出,yield语句就相当于程序中的断点,每个yield语句中断一次。
def letter_range(a,z):
yield 'a'
yield 'b'
yield 'c'
f=letter_range('a','z')
print(f.__next__())
print(f.__next__())
print(f.__next__())
print(f.__next__())
a
b
c
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-104-931315ae73c1> in <module>
7 print(f.__next__())
8 print(f.__next__())
----> 9 print(f.__next__())
StopIteration:
可迭代的(iterable)对象是支持in操作符可以逐一迭代访问对象中的数据。迭代器(iterator)是定义了__iter__ 和__next__函数的对象。可迭代对象不一定是迭代器,但迭代器一定是可迭代的。list,dic 等对象都是可迭代的,但不是迭代器。
生成器是一种特殊的迭代器,没有显示的定义__iter__ 和 __next__ 函数,通过生成器表达式,如:(i for in range(100))或yield语句实现。
通过python内置函数next(<iterator>)可以访问迭代器的下一个值,也可以通过<iterator>.send(<new value>)来给迭代器设置新的迭代值。next() 和 send() 函数的原理基本一致,都是恢复生成器继续运行,直到调用了下一次yield语句而重新中断。yield语句是生成一个新的value给调用者,但yield语句本身的返回值不确定,next函数和send函数唯一的区别就在于,通过next函数调用的yield语句返回一个None,而send函数调用的yield语句则返回send函数指定的值,只要在生成器函数中获取这个值就可以实现在迭代过程中指定新的迭代值了。但为了能接收新设的迭代值,迭代器需要做些改动,首先获取yiel的语句的返回值,并判断是否为None,如果是None则是由next函数调用,如果不是None则是由send函数调用,从而可以给yiel语句设置新值。如下示例:
def gensample():
a=0
while a <10:
r=yield a
if r is None:
a=a+1
else:
a=r
g=gensample()
a=next(g)
print(a)
a=g.send(8)
print(a)
a=next(g)
print(a)
0
8
9
由此可以看出next()和send(None)的功能是一样的。需要提醒的是,第一次调用时,请使用next()语句或是send(None),不能使用send发送一个非None的值,否则会出错的,因为没有Python yield语句来接收这个值。
生成器也可以用于作业协作,因为yield语句有断点中断的效果,可以在yield语句挂起等待一个结果,收到结果后继续执行。