Python yield 生成器
Python yield 生成器
yield 生成器函数在生成值后自动挂起,并且暂停它的执行和状态,常常在从头计算整个系列的值或者手动保存和恢复类中的状态时,作为一种解决方案。
生成器在被挂起时自动保存状态,yield将函数挂起后将向调用者返回一个值。
示例:
#!/usr/bin/env python def gen_squres(N): for i in range(N): print("in gen_squres") yield i**2 for j in gen_squres(5): print("j: " + str(j))
运行结果:
in gen_squres
j: 0
in gen_squres
j: 1
in gen_squres
j: 4
in gen_squres
j: 9
in gen_squres
j: 16
代码分析:
////////////////经过单步执行 可以看到(1)和(2)是交替执行的
>>> def gen_squres(N): //// (1)
... for i in range(N):
... yield i**2
...
>>> for j in gen_squres(5): //// (2)
... print j, ':',
...
0 : 1 : 4 : 9 : 16 :
yield 机理
for循环与生成器工作起来是一样的:通过重复调用next方法,直到捕获一个异常。
函数返回的是一个生成器对象,支持迭代器协议,也就是说,next方法可以开始这个函数或者从它上次yield值后的地方回复,以及在得到一系列的值的最后一个时,产生StopIteration
>>> h = gen_squres(5)
>>> h.next()
0
>>> h.next()
1
>>> h.next()
4
>>> h.next()
9
>>> h.next()
16
>>> h.next()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
Python yield 协程
Python编程中,我们经常会看到函数中带有yield关键字,但请注意,此时的函数不再是我们熟知的一般函数,二是所谓的generator(生成器)
生成器
对于生成器,可以对比于列表来看,我们在循环代码中经常会使用range()产生一个list对象,继而在for循环下依次遍历,
for i in range(1000):
print i
或者是使用列表生成式生成一个list对象:
[x * x for x in range(1000)]
上面list实现,确实很方便,但这有个很大的缺点,即所生产的list对象在程序运行过程中是存放在内存中,占用内存大小与list规模有关,若要在编程时控制内存的占用,最好不要使用list。
相比于list对象对内存的占用,generator有很大的优势,generator保存的是算法,不会生成所有的元素,而只是在调用next()时产生一个元素,很好的优化了内存占用的问题,可以通过next方法访问数据,当没有数据时会自动抛出StopIteration异常
>>> g = (x * x for x in [1, 2, 3])
>>> g
<generator object <genexpr> at 0x02534968>
>>> g.next()
1
>>> g.next()
4
>>> g.next()
9
>>> g.next()
Traceback (most recent call last):
File "<pyshell#16>", line 1, in <module>
g.next()
StopIteration
>>>
这么做的话难免有些繁琐,还好在for循环中会帮我们实现next方法的调用也可以这么实现
>>> for i in g:
print i
yield 初体验
以上所实现的generator只是规律十分简单的,这很好实现,只需要类似于列表生成式的简单语法即可,那么对于其他的数列计算如何实现呢,例如斐波那契数列,它的定义虽然简单:除第一、二个数据外,所有的数据均是其前两个数据之和;如果我们通过一般函数实现,无疑当数列规模很大时,占用大量内存
>>> def fib(N):
n, a, b = 0, 0, 1
while n < N:
print b
a, b = b, a + b
n = n + 1
>>> fib(5)
1
1
2
3
5
那么如何将上述方法转换为generator加以实现呢,很简单,只需要将print b 替换为yield b即可,我们可以试一下:
>>> def fib(N):
n, a, b = 0, 0, 1
while n < N:
yield b
a, b = b, a + b
n = n + 1
>>> fib(5)
<generator object fib at 0x02530F80>
>>> for i in fib(5):
print i
1
1
2
3
5
加了yield关键字后的函数是如何执行的呢,不应该说是函数,这时应该称为生成器 generator
我们调用fib(5)并不会执行函数,而是返回一个generator对象,真正的执行是在调用next方法(for循环中自动调用next()),每次循环都会执行fib内的代码,遇到yield则返回一个迭代值(类似于中断);
在下次循环时执行yield的下一语句,直至遇到下一个yield。
yield 协程
协程(coroutine)也叫微线程,相比于多线程更为高效,因为协程是多个程序在一个线程中执行,没有线程间切换的开销;同时在协程中不需要加锁机制,因为在一个线程中不存在变量冲突问题。
例如:经典问题(生产者-消费者问题)就可以使用协程机制实现,相比于多线程更为高效
def consumer():
r = ''
while True:
n = yield r
if not n:
return
print 'consumer %s' % n
r = 'OK'
def produce(c):
c.next()
n = 0
while n < 5:
n = n + 1
print 'produce %s' % n
r = c.send(n)
print 'consumer return %s' % r
c.close()
if __name__ == '__main__':
c = consumer()
produce(c)
produce 1
consumer 1
consumer return OK
produce 2
consumer 2
consumer return OK
produce 3
consumer 3
consumer return OK
produce 4
consumer 4
consumer return OK
produce 5
consumer 5
consumer return OK
在上述代码中,consumer是一个生成器,执行过程中首先通过consumer产生generator对象c,
我们在执行到produce(c)的next方法时,才切换到生成器函数consumer中执行,
在consumer中遇到yield中断,又切回到produce中
在produce中的c.send(n):主要干两件事:
1. 将n添加到生成器中,
2. 返回下一个yield值(return next());
所以当我们运行到send方法时,内含next机制进而切换到consumer函数中执行(传入参数n),得到返回值'OK'(在下一个yield中返回)
。。。。。
最后在produce中关闭迭代器c.close()
总结
- 在generator中不同于一般函数,调用方法名不会执行,只会返回一个generator对象,只有在调用next方法时才会执行
- 一个函数中加入yield则变为generator,函数执行到yield时中断,下次迭代时定位到yield的下一条语句;yield还常用于文件的读取,用read()会造成不可预测的内存占用问题,而使用yield可以实现内存只存储每次迭代过程中固定的size
版权所有: 本文系米扑博客原创、转载、摘录,或修订后发表,最后更新于 2015-01-13 14:14:47
侵权处理: 本个人博客,不盈利,若侵犯了您的作品权,请联系博主删除,莫恶意,索钱财,感谢!
转载注明: Python yield 生成器 (米扑博客)