私自认为,python的生成器是Python语言中比较深奥的问题,始于简单的迭代器,终于复杂的协程、调度问题。
本文从迭代器协议开始,逐步介绍生成器的原理概念,最后生成器在协程中的复杂应用。
1、实现迭代器协议:
可迭代对象: 序列对象,如列表list,元祖tuple,字典dict,集合set都是可迭代对象;
Python的迭代器协议需要 __iter__()
方法返回一个实现了 __next__()
方法的迭代器对象;
使用生成器创建新的迭代模式:
def frange(start, stop, increment):
x = start
f
yield x
x += increment
>>> for i in frange(0,4,0.5):
... print(i)
...
0
0.5
1.0
1.5
2.0
2.5
3.0
3.5
实现迭代器协议:重写__iter__(),使之返回一个实现了__next__()方法的对象.
class Node:
def __init__(self, value):
self._value = value
self._children = []
def __repr__(self):
return 'Node({!r})'.format(self._value)
def add_child(self, node):
self._children.append(node)
def __iter__(self):
return iter(self._children)
def depth_first(self):
yield self
for c in self:
yield from c.depth_first()
# Example
if __name__ == '__main__':
root = Node(0)
child1 = Node(1)
child2 = Node(2)
root.add_child(child1)
root.add_child(child2)
child1.add_child(Node(3))
child1.add_child(Node(4))
child2.add_child(Node(5))
for ch in root.depth_first():
print(ch)
# Outputs Node(0), Node(1), Node(3), Node(4), Node(2), Node(5)
迭代器以其延迟计算(lazy evaluation)的属性,在需要读取大量数据的应用场景可以搞笑的节省内存.
2、生成器函数
函数中有一条yield语句就叫做生成器函数。普通函数用return返回值,在生成器函数中,yield也可以用来返回值。生成器被调用时返回一个生成器对象,生成器对象本质上还是一个迭代器,但是生成器更加简洁。
>>> frange(0,4,0.5)
<generator object frange at 0x7fb18d1433b8>
3、生成器实现协程
协程(coroutine)又称为微线程,子程序,或者称为函数,在所有语言中都是层级调用,比如A调用B,B在执行过程中又调用了C,C执行完毕返回,B执行完毕返回,最后是A执行完毕。所以子程序调用是通过栈实现的,一个线程就是执行一个子程序。子程序调用总是一个入口,一次返回,调用顺序是明确的。而协程的调用和子程序不同。
协程看上去也是子程序,但执行过程中,在子程序内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行(类似于CPU中断,而不是函数调用)。
def A():
print '1'
print '2'
print '3'
def B():
print 'x'
print 'y'
print 'z'
协程的执行结果:
1
2
x
y
3
z
看起来A、B的执行有点像多线程,但协程的特点在于是一个线程执行,那和多线程比,协程优势有两点:
第一个优势就是协程极高的执行效率。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。
第二大优势就是不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。
因为协程是一个线程执行,那怎么利用多核CPU呢?最简单的方法是多进程+协程,既充分利用多核,又充分发挥协程的高效率,可获得极高的性能。
以“生产者、消费者”模型为例说明生成器实现的协程工作原理:
传统的“生产者、消费者”模型通过多线程实现,占用内存高、易出现死锁。如果改用协程,生产者生产消息后,直接通过yield跳转到消费者开始执行,待消费者执行完毕后,切换回生产者继续生产,效率极高:
import time
def consumer():
r = ''
while True:
n = yield r
if not n:
return
print('[CONSUMER] Consuming %s...' % n)
time.sleep(1)
r = '200 OK'
def produce(c):
#c.next()
c.send(None)
n = 0
while n < 5:
n = n + 1
print('[PRODUCER] Producing %s...' % n)
r = c.send(n)
print('[PRODUCER] Consumer return: %s' % r)
c.close()
if __name__=='__main__':
c = consumer()
produce(c)
执行结果:
[PRODUCER]Producing 1...
[CONSUMER]Consuming 1...
[PRODUCER]Consumer return: 200 OK
[PRODUCER]Producing 2...
[CONSUMER]Consuming 2...
[PRODUCER]Consumer return: 200 OK
[PRODUCER]Producing 3...
[CONSUMER]Consuming 3...
[PRODUCER]Consumer return: 200 OK
[PRODUCER]Producing 4...
[CONSUMER]Consuming 4...
[PRODUCER]Consumer return: 200 OK
[PRODUCER]Producing 5...
[CONSUMER]Consuming 5...
[PRODUCER]Consumer return: 200 OK
consumer()就是一个生成器函数,将consumer函数对象传入producer():
1、send(None)启动生成器;(python2中使用next())
2、producer生产了东西,通过c.send(n)传递给consumer中的yield表达式,切换到consumer执行;
3、consumer返回处理结果,即c.send(n)的返回结果,具体的,是yield表达式后面的值;
4、producer继续生产,直到关闭consumer;
生成器实现的协程中,yield常伴随send()使用,官方文档中的send()解释: