Python之美[从菜鸟到高手]--生成器之全景分析

来源:互联网 发布:mac怎么关闭应用程序 编辑:程序博客网 时间:2024/04/30 14:00

yield指令可以暂停一个函数,并且返回中间结果。只要函数中包含了yield关键字,该函数调用就是生成器对象。

------------------------

yield指令,可以暂停一个函数并返回中间结果。使用该指令的函数将保存执行环境,并且在必要时恢复。

生成器比迭代器更加强大也更加复杂,需要花点功夫好好理解贯通。

看下面一段代码:

[python] view plaincopy
  1. def gen():  
  2.     for x in xrange(4):  
  3.         tmp = yield x  
  4.         if tmp == 'hello':  
  5.             print 'world'  
  6.         else:  
  7.             print str(tmp)  

     只要函数中包含yield关键字,该函数调用就是生成器对象。
[python] view plaincopy
  1. g=gen()  
  2. print g   #<generator object gen at 0x02801760>  
  3. print isinstance(g,types.GeneratorType) #True  
    我们可以看到,gen()并不是函数调用,而是产生生成器对象。


   生成器对象支持几个方法,如gen.next() ,gen.send() ,gen.throw()等。

[python] view plaincopy
  1. print g.next() # 0  
    调用生成器的next方法,将运行到yield位置,此时暂停执行环境,并返回yield后的值。所以打印出的是0,暂停执行环境。
[python] view plaincopy
  1. print g.next() #None  1  
     再调用next方法,你也许会好奇,为啥打印出两个值,不急,且听我慢慢道来。

     上一次调用next,执行到yield 0暂停,再次执行恢复环境,给tmp赋值(注意:这里的tmp的值并不是x的值,而是通过send方法接受的值),由于我们没有调用send方法,所以

tmp的值为None,此时输出None,并执行到下一次yield x,所以又输出1.

      到了这里,next方法我们都懂了,下面看看send方法。

[python] view plaincopy
  1. print g.send('hello'#world  2  
      上一次执行到yield 1后暂停,此时我们send('hello'),那么程序将收到‘hello',并给tmp赋值为’hello',此时tmp=='hello'为真,所以输出'world',并执行到下一次yield 2,所以又打印出2.(next()等价于send(None))

      当循环结束,将抛出StopIteration停止生成器。

      看下面代码:

[python] view plaincopy
  1. def stop_immediately(name):  
  2.     if name == 'skycrab':  
  3.         yield 'okok'  
  4.     else:  
  5.         print 'nono'  
  6.   
  7. s=stop_immediately('sky')  
  8. s.next()  
正如你所预料的,打印出’nono',由于没有额外的yield,所以将直接抛出StopIteration。
[python] view plaincopy
  1. nono  
  2. Traceback (most recent call last):  
  3.   File "F:\python workspace\Pytest\src\cs.py", line 170in <module>  
  4.     s.next()  
  5. StopIteration  
      看下面代码,理解throw方法,throw主要是向生成器发送异常。
[python] view plaincopy
  1. def mygen():  
  2.     try:  
  3.         yield 'something'  
  4.     except ValueError:  
  5.         yield 'value error'  
  6.     finally:  
  7.         print 'clean'  #一定会被执行  
  8. gg=mygen()  
  9. print gg.next() #something  
  10. print gg.throw(ValueError) #value error  clean  
     调用gg.next很明显此时输出‘something’,并在yield ‘something’暂停,此时向gg发送ValueError异常,恢复执行环境,except  将会捕捉,并输出信息。

     理解了这些,我们就可以向协同程序发起攻击了,所谓协同程序也就是是可以挂起,恢复,有多个进入点。其实说白了,也就是说多个函数可以同时进行,可以相互之间发送消息等。

     这里有必要说一下multitask模块(不是标准库中的),看一段multitask使用的简单代码:

[python] view plaincopy
  1. def tt():  
  2.     for x in xrange(4):  
  3.         print 'tt'+str(x)  
  4.         yield  
  5.   
  6. def gg():  
  7.     for x in xrange(4):  
  8.         print 'xx'+str(x)  
  9.         yield  
  10.   
  11. t=multitask.TaskManager()  
  12. t.add(tt())  
  13. t.add(gg())  
  14. t.run()  

结果:
[python] view plaincopy
  1. tt0  
  2. xx0  
  3. tt1  
  4. xx1  
  5. tt2  
  6. xx2  
  7. tt3  
  8. xx3  

   如果不是使用生成器,那么要实现上面现象,即函数交错输出,那么只能使用线程了,所以生成器给我们提供了更广阔的前景。 

   如果仅仅是实现上面的效果,其实很简单,我们可以自己写一个。主要思路就是将生成器对象放入队列,执行send(None)后,如果没有抛出StopIteration,将该生成器对象再加入队列。

[python] view plaincopy
  1. class Task():  
  2.     def __init__(self):  
  3.         self._queue = Queue.Queue()  
  4.   
  5.     def add(self,gen):  
  6.         self._queue.put(gen)  
  7.   
  8.     def run(self):  
  9.         while not self._queue.empty():  
  10.             for i in xrange(self._queue.qsize()):  
  11.                 try:  
  12.                     gen= self._queue.get()  
  13.                     gen.send(None)  
  14.                 except StopIteration:  
  15.                     pass  
  16.                 else:  
  17.                     self._queue.put(gen)  
  18.   
  19. t=Task()  
  20. t.add(tt())  
  21. t.add(gg())  
  22. t.run()  

  当然,multitask实现的肯定不止这个功能,有兴趣的童鞋可以看下源码,还是比较简单易懂的。


#增补 2014/5/21

之前我在南京面试Python时遇到这么一道题目:

[python] view plaincopy
  1. def thread1():  
  2.     for x in range(4):  
  3.         yield  x  
  4.           
  5.   
  6. def thread2():  
  7.     for x in range(4,8):  
  8.         yield  x  
  9.           
  10.   
  11. threads=[]  
  12. threads.append(thread1())  
  13. threads.append(thread2())  
  14.   
  15.   
  16. def run(threads): #写这个函数,模拟线程并发  
  17.     pass  
  18.   
  19. run(threads)  
如果上面class Task看懂了,那么这题很简单,其实就是考你用yield模拟线程调度,解决如下:
[python] view plaincopy
  1. def run(threads):  
  2.     for t in threads:  
  3.         try:  
  4.             print t.next()  
  5.         except StopIteration:  
  6.             pass  
  7.         else:  
  8.             threads.append(t) 
0 0
原创粉丝点击