python的生成器

私自认为,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()解释:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值