Python yield的理解与简单测试

来源:互联网 发布:mac os 10.11镜像下载 编辑:程序博客网 时间:2024/05/29 08:23

在学习异步IO,会涉及到协程,最简单的协程,可以用yield来模拟,最初学习yield的时候就没搞太明白,今天看了些博客,终于搞得懂了。

下面给出一个作者的总结的知识点,让我理解了。http://www.jianshu.com/p/d09778f4e055,谢谢


主要的掌握


1.通常的for...in...循环中,in后面是一个数组,这个数组就是一个可迭代对象,类似的还有链表,字符串,文件。它可以是mylist = [1, 2, 3],也可以是mylist = [x*x for x in range(3)]。
它的缺陷是所有数据都在内存中,如果有海量数据的话将会非常耗内存。

2.生成器是可以迭代的,但只可以读取它一次。因为用的时候才生成。比如 mygenerator = (x*x for x in range(3)),注意这里用到了(),它就不是数组,而上面的例子是[]。

3.我理解的生成器(generator)能够迭代的关键是它有一个next()方法,工作原理就是通过重复调用next()方法,直到捕获一个异常。可以用上面的mygenerator测试。

4.带有 yield 的函数不再是一个普通函数,而是一个生成器generator,可用于迭代,工作原理同上。

5.yield 是一个类似 return 的关键字,迭代一次遇到yield时就返回yield后面的值。重点是:下一次迭代时,从上一次迭代遇到的yield后面的代码开始执行。

6.简要理解:yield就是 return 返回一个值,并且记住这个返回的位置,下次迭代就从这个位置后开始。

7.带有yield的函数不仅仅只用于for循环中,而且可用于某个函数的参数,只要这个函数的参数允许迭代参数。比如array.extend函数,它的原型是array.extend(iterable)。

8.send(msg)与next()的区别在于send可以传递参数给yield表达式,这时传递的参数会作为yield表达式的值(这里应该理解到yield表达式,它是一个表达式,而不仅仅yield产生一个值),而yield的参数是返回给调用者的值。——换句话说,就是send可以强行修改上一个yield表达式值。比如函数中有一个yield赋值,a = yield 5,第一次迭代到这里会返回5,a还没有赋值。第二次迭代时,使用.send(10),那么,就是强行修改yield 5表达式的值为10,本来是5的,那么a=10

9.send(msg)与next()都有返回值,它们的返回值是当前迭代遇到yield时,yield后面表达式的值,其实就是当前迭代中yield后面的参数。

10.第一次调用时必须先next()或send(None),否则会报错,send后之所以为None是因为这时候没有上一个yield(根据第8条)。可以认为,next()等同于send(None)。

#encoding:UTF-8def gener():    print "1"    x=yield "hello"    print "2","x=",x    y=5+(yield x)    print "3","y=",yg=gener()print g.next()#g.send(None)'''这里第一次调用时的两种方法输出:1hello    #程序到yield就结束,下次调用时从断开处继续(这个特点会用在协程,异步IO,并发技术中)'''print g.send(5)'''继上一次断处执行print "2","x=",x        y=5+(yield x)由于send(5),那么上一次yield表达式的值就位5,所以x=5然后运行到yield x,则输出yield后面的参数5'''print g.send(2)'''此时send(2),则上一次yield的表达式的值为2则y=7'''




二、使用yield实现协程



#coding=utf-8
__author__ = "Zhangxiaozi"

#!/usr/bin/python

def consumer():
    r = ''
    while True:
        n = yield r  #消费者通过yield表达式拿到消息,再把yield的参数当做反馈结果反馈给生产者
        if not n:
            print("not n...")
            return
        print('[CONSUMER] Consuming %s...' % n)
        r = '200 OK'

def produce(c):
    f = c.send(None)  #第一次f等于''
    print('[PRODUCER] Consumer first return: %s' % f)
    n = 0
    while n < 2:
        n = n + 1
        print('[PRODUCER] Producing %s...' % n)
        # 一旦产生东西就通过r = c.send(n)切换到消费者consumer执行
        r = c.send(n)
        print('[PRODUCER] Consumer return: %s' % r)
    c.close()

c = consumer()
produce(c)




程序结果:
[PRODUCER] Consumer first return:
[PRODUCER] Producing 1...
[CONSUMER] Consuming 1...
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 2...
[CONSUMER] Consuming 2...
[PRODUCER] Consumer return: 200 OK



协程的说明:

注意到consumer函数是一个generator,把一个consumer传入produce后:

  1. 首先调用c.send(None)启动生成器;
  2. 然后,一旦生产了东西,通过c.send(n)切换到consumer执行;
  3. consumer通过yield拿到消息,处理,又通过yield把结果传回;
  4. produce拿到consumer处理的结果,继续生产下一条消息;
  5. produce决定不生产了,通过c.close()关闭consumer,整个过程结束。
整个流程无锁,由一个线程执行,produce和consumer协作完成任务,所以称为“协程”,而非线程的抢占式多任务。