您的位置:首页 > 编程语言 > Python开发

深入理解Python中的生成器(转载)

2017-01-08 22:18 585 查看

生成器(generator)概念

生成器不会把结果保存在一个系列中,而是保存生成器的状态,在每次进行迭代时返回一个值,直到遇到
StopIteration
异常结束。

生成器语法

生成器表达式: 通列表解析语法,只不过把列表解析的[]换成()

生成器表达式能做的事情列表解析基本都能处理,只不过在需要处理的序列比较大时,列表解析比较费内存。

>>> gen = (x**2 for x in range(5))
>>> gen
<generator object <genexpr> at 0x0000000002FB7B40>
>>> for g in gen:
...   print(g, end='-')
...
0-1-4-9-16-
>>> for x in [0,1,2,3,4,5]:
...   print(x, end='-')
...
0-1-2-3-4-5-


生成器函数: 在函数中如果出现了
yield
关键字,那么该函数就不再是普通函数,而是生成器函数。

但是生成器函数可以生产一个无线的序列,这样列表根本没有办法进行处理。

yield
的作用就是把一个函数变成一个
generator
,带有
yield
的函数不再是一个普通函数,Python 解释器会将其视为一个
generator


下面为一个可以无穷生产奇数的生成器函数。

def odd():
n=1
while True:
yield n
n+=2
odd_num = odd()
count = 0
for o in odd_num:
if count >=5: break
print(o)
count +=1


当然通过手动编写迭代器可以实现类似的效果,只不过生成器更加直观易懂

class Iter:
def __init__(self):
self.start=-1
def __iter__(self):
return self
def __next__(self):
self.start +=2
return self.start
I = Iter()
for count in range(5):
print(next(I))


题外话: 生成器是包含有
__iter__()
__next__()
方法的,所以可以直接使用for来迭代,而没有包含
StopIteration
的自编
Iter
来只能通过手动循环来迭代。

>>> from collections import Iterable
>>> from collections import Iterator
>>> isinstance(odd_num, Iterable)
True
>>> isinstance(odd_num, Iterator)
True
>>> iter(odd_num) is odd_num
True
>>> help(odd_num)
Help on generator object:

odd = class generator(object)
|  Methods defined here:
|
|  __iter__(self, /)
|      Implement iter(self).
|
|  __next__(self, /)
|      Implement next(self).
......


看到上面的结果,现在你可以很有信心的按照Iterator的方式进行循环了吧!

for
循环执行时,每次循环都会执行
fab
函数内部的代码,执行到
yield b
时,
fab
函数就返回一个迭代值,下次迭代时,代码从
yield b
的下一条语句继续执行,而函数的本地变量看起来和上次中断执行前是完全一样的,于是函数继续执行,直到再次遇到
yield
。看起来就好像一个函数在正常执行的过程中被
yield
中断了数次,每次中断都会通过
yield
返回当前的迭代值。

yield 与 return

在一个生成器中,如果没有
return
,则默认执行到函数完毕时返回
StopIteration


>>> def g1():
...     yield 1
...
>>> g=g1()
>>> next(g)    #第一次调用next(g)时,会在执行完yield语句后挂起,所以此时程序并没有执行结束。
1
>>> next(g)    #程序试图从yield语句的下一条语句开始执行,发现已经到了结尾,所以抛出StopIteration异常。
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
>>>


如果遇到
return
,如果在执行过程中
return
,则直接抛出
StopIteration
终止迭代。

>>> def g2():
...     yield 'a'
...     return
...     yield 'b'
...
>>> g=g2()
>>> next(g)    #程序停留在执行完yield 'a'语句后的位置。
'a'
>>> next(g)    #程序发现下一条语句是return,所以抛出StopIteration异常,这样yield 'b'语句永远也不会执行。
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration


如果在
return
后返回一个值,那么这个值为
StopIteration
异常的说明,不是程序的返回值。

生成器没有办法使用return来返回值。

>>> def g3():
...     yield 'hello'
...     return 'world'
...
>>> g=g3()
>>> next(g)
'hello'
>>> next(g)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration: world


生成器支持的方法

>>> help(odd_num)
Help on generator object:

odd = class generator(object)
|  Methods defined here:
......
|  close(...)
|      close() -> raise GeneratorExit inside generator.
|
|  send(...)
|      send(arg) -> send 'arg' into generator,
|      return next yielded value or raise StopIteration.
|
|  throw(...)
|      throw(typ[,val[,tb]]) -> raise exception in generator,
|      return next yielded value or raise StopIteration.


close()

手动关闭生成器函数,后面的调用会直接返回
StopIteration
异常。

>>> def g4():
...     yield 1
...     yield 2
...     yield 3
...
>>> g=g4()
>>> next(g)
1
>>> g.close()
>>> next(g)    #关闭后,yield 2和yield 3语句将不再起作用
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration


send()

生成器函数最大的特点是可以接受外部传入的
cef8
一个变量,并根据变量内容计算结果后返回。

这是生成器函数最难理解的地方,也是最重要的地方,实现后面我会讲到的协程就全靠它了。

def gen():
value=0
while True:
receive=yield value
if receive=='e':
break
value = 'got: %s' % receive

g=gen()
print(g.send(None))
print(g.send('aaa'))
print(g.send(3))
print(g.send('e'))


执行流程:

通过
g.send(None)
或者
next(g)
可以启动生成器函数,并执行到第一个
yield
语句结束的位置。

此时,执行完了
yield
语句,但是没有给
receive
赋值。
yield value
会输出初始值0。注意:在启动生成器函数时只能send(None),如果试图输入其它的值都会得到错误提示信息。

通过
g.send('aaa')
,会传入
aaa
,并赋值给
receive
,然后计算出
value
的值,并回到
while
头部,执行
yield value
语句有停止。 此时
yield value
会输出
"got: aaa"
,然后挂起。

通过
g.send(3)
,会重复第2步,最后输出结果为
"got: 3"


当我们
g.send('e')
时,程序会执行
break
然后推出循环,最后整个函数执行完毕,所以会得到
StopIteration
异常。最后的执行结果如下:

0
got: aaa
got: 3
Traceback (most recent call last):
File "h.py", line 14, in <module>
print(g.send('e'))
StopIteration


throw()

用来向生成器函数送入一个异常,可以结束系统定义的异常,或者自定义的异常。

throw()
后直接跑出异常并结束程序,或者消耗掉一个
yield
,或者在没有下一个
yield
的时候直接进行到程序的结尾。

def gen():
while True:
try:
yield 'normal value'
yield 'normal value 2'
print('here')
except ValueError:
print('we got ValueError here')
except TypeError:
break

g=gen()
print(next(g))
print(g.throw(ValueError))
print(next(g))
print(g.throw(TypeError))


输出结果为:

normal value
we got ValueError here
normal value
normal value 2
Traceback (most recent call last):
File "h.py", line 15, in <module>
print(g.throw(TypeError))
StopIteration


解释:

print(next(g))
:会输出
normal value
,并停留在
yield 'normal value 2'
之前。

由于执行了
g.throw(ValueError)
,所以会跳过所有后续的
try
语句,也就是说
yield 'normal

value 2'
不会被执行,然后进入到
except
语句,打印出
we got ValueError here


然后再次进入到while语句部分,消耗一个
yield
,所以会输出
normal value


print(next(g))
,会执行yield
'normal value 2'
语句,并停留在执行完该语句后的位置。

g.throw(TypeError)
:会跳出
try
语句,从而
print('here')
不会被执行,然后执行break语句,跳出while循环,然后到达程序结尾,所以跑出
StopIteration
异常。

下面给出一个综合例子,用来把一个多维列表展开,或者说扁平化多维列表)

def flatten(nested):

try:
#如果是字符串,那么手动抛出TypeError。
if isinstance(nested, str):
raise TypeError
for sublist in nested:
#yield flatten(sublist)
for element in flatten(sublist):
#yield element
print('got:', element)
except TypeError:
#print('here')
yield nested

L=['aaadf',[1,2,3],2,4,[5,[6,[8,[9]],'ddf'],7]]
for num in flatten(L):
print(num)


如果理解起来有点困难,那么把print语句的注释打开在进行查看就比较明了了。

yield from

yield
产生的函数就是一个迭代器,所以我们通常会把它放在循环语句中进行输出结果。

有时候我们需要把这个
yield
产生的迭代器放在另一个生成器函数中,也就是生成器嵌套。

比如下面的例子:

def inner():
for i in range(10):
yield i
def outer():
g_inner=inner()    #这是一个生成器
while True:
res = g_inner.send(None)
yield res

g_outer=outer()
while True:
try:
print(g_outer.send(None))
except StopIteration:
break


此时,我们可以采用
yield from
语句来减少我么你的工作量。

def outer2():
yield from inner()


当然 ,
yield from
语句的重点是帮我们自动处理内外层之间的异常问题,这里有2篇写的很好的文章,所以我就不再啰嗦了。

http://blog.theerrorlog.com/yield-from-in-python-3.html

http://stackoverflow.com/questions/9708902/in-practice-what-are-the-main-uses-for-the-new-yield-from-syntax-in-python-3

总结

按照鸭子模型理论,生成器就是一种迭代器,可以使用
for
进行迭代。

第一次执行
next(generator)
时,会执行完
yield
语句后程序进行挂起,所有的参数和状态会进行保存。

再一次执行
next(generator)
时,会从挂起的状态开始往后执行。

在遇到程序的结尾或者遇到
StopIteration
时,循环结束。

可以通过
generator.send(arg)
来传入参数,这是协程模型。

可以通过
generator.throw(exception)
来传入一个异常。
throw
语句会消耗掉一个
yield


可以通过
generator.close()
来手动关闭生成器。

next()
等价于
send(None)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  python 生成器 yield