python的生成器和协程

󰃭 2016-04-13

python的生成器和协程都是用yield实现的

以下实践基于python2.7, 使用python3的小伙伴将print 和 next 调用改为python3的用法即可

生成器

在python中, 我们可以用列表表达式生成一个list, 很方便, 如下:

m = [ x for x in range(0,10)]
print m #[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

这样我们就能很简单的获取生成一个list了, 但假如, 我们需要生成的list对象很大, 那这种生成方式就可能撑爆内存了,就直接内存溢出了

这时候,我们就可以用生成器了, 假如我们需要打印100000000000000 以内的所有偶数, 如下

def ou_output():
    c = 0
    while c < 100000000000000: 
        if c % 2 == 0:
            yield c
        c += 1
m = out_output()
print m#<generator object ou_output at 0x7f21f71a2eb0>

这里我们得到的m, 并不是函数的返回结果, 而是一个生成器(generator)类型的对象, 他保存了yield 的上下文关系, 在next的时候计算出结果, 然后返回, 如下

m.next() #0
m.next() #2
m.next() #4

这样, 只是每次在需要的时候计算出结果返回, 就不会占用太多的内存了,不过这样也增加了cpu的计算压力。

##协程

python 进程并发处理的时候, 有几种方式,

多进程 多线程 协程

多进程和多线程就不说了, 比较常见, 协程的概念可能大多数人都不太熟悉

在上面的生成器的例子中, 我们看到包含yield的function 都是生成器, 在执行到yield的位置的时候, 返回了yield的结果,跳出生成器, 执行外面的逻辑, next(实际上还有一个send, 也有类似于next的功能, 还能像生成器内部传递动态参数)后又进入到生成器里面, 这样循环, 类似于这种中断执行, 协同处理的方式就是协程的实现,

举个例子, A和B两个人, A负责读取一个目录列表(比如人名),B 负责将内容写在黑板上, A读一个, B写一个。 这两个过程就就像是生产+消费者模式的一个简单的实例, producer A, consumer B。 传统的并发实现用多进程的话, 进程通信不方便(当然,也是可以通过Queue等方式进行通信), 使用多线程编程的话, 会有死锁的问题, 如果用协程的方式来实现, 就很方便

如下

def writerB():
    write_suc = False
    while(True):
        name = yield write_suc #send 进来的name, 注意, 这里的write_suc 会在下一个循环的时候yield, 作为到send的执行结果返回回去, 和上面的生成器例子里的yield c的 c 一样
        print "write on blackbord: %s" %name
        write_suc = True
        time.sleep(1)

def readerA(wb):
    names = [
        "one fisher",
        "Yi_Zhi_Yu",
        "Tony Wang",
    ]
    wb.next() #生成器运行前要先执行, 类似于开始执行
    for name in names:
        print "read name: %s" %name
        write_suc = wb.send(name) #将name 发送到wb生成器的name, 进入wb中执行
    wb.close()

if __name__ == "__main__":
    wb = writerB()
    readerA(wb)

结果如下:

read name: one fisher
write on blackbord: one fisher
read name: Yi_Zhi_Yu
write on blackbord: Yi_Zhi_Yu
read name: Tony Wang
write on blackbord: Tony Wang

在每次send的时候, readerA的执行就会被中断, 进入writerB里执行了, 再到下一个yield的时候返回到readerA中继续执行, 这样, A读-B写 – A读-B写 – A读-B写 整个执行单线程无锁, reader和writer协同实现