对Python中yield的理解

时间:2022-07-14 23:30:15

     最近要写一个程序,主要是用Python,之前也曾经接触过一些Python,但也只是基础,已经忘得差不多一干二净了!现在只能突击下啦!学到yield的时候遇到了不少问题,现在总结一下自己对yield的理解,希望和大家交流!

  OK,今天的主题是yield,这是和Python中迭代相关的一个概念,所以一定要先了解迭代,例如,我们在for循环中一个可迭代的对象,比如一个列表ls=[1,2,3,4],我们用for来迭代访问他的话就是for x in ls: print x,之所以可以这样,是因为列表对象都实现了__iterator__方法,还有next方法,每次对ls迭代实际上就是调用一次next方法,而next方法会返回当前当前访问的数据然后再赋给接收它的x。我们就可以在循环体里面用x做我们想做的操作了!

def fun():
 print 'first'
 yield 1	;④
 print 'second'
 yield 2	;⑤
 print 'third'

f=fun()
x=f.next() ;①
print x
f.next()	;②
f.next()	;③

 

运行的结果是:

first

1

second

third

Traceback (most recent call last):

  File "D:/develop/Python26/e.py", line 12, in <module>

    f.next()

StopIteration

  如果你在IDLE里面直接打f回车你会看到现实f是一个generator,也就是说可以对他进行迭代访问,根据上面的程序及结果我们看看显式调用f的next方法看看对yield怎么处理,程序运行到①,程序就会进到fun中从最开是执行指导找到下一个yield语句也就是④,并把yield后的值作为next方法的返回值传出去,所以你会看到程序最先输出first,然后回到①,x接收到返回的值,再把x的值输出去所以会打印1。程序接着向下执行到②,然后跳到fun中从上次暂停的地方(也就是④)的下一条语句开始执行直到遇到下一个yield也就是⑤,这中间的都会执行,所以屏幕会打印出second然后把⑤处yield后的2作为next的返回值,但是②处并没有变量接收它,程序再执行到③,跳到fun中接着上次暂停的地方即⑤的下一句开始执行,直到下一个yield位置,但是我们看到fun中已经没有下一个yield了,所以这次迭代他会一直运行到结束,然后抛出个StopIteration异常,我们就会看到上面程序最后悔输出third然后再抛出一个异常。当然你也可以在上面代码中加上try...except...来捕获这个异常。

  但是我们再看看在for中迭代包含yield的函数的情况(其实在for中每次还是调用fun1的next方法,但是这个next会和yield结合起来产生作用),比如有如下代码:

def fun1():
    print 'first'  ;①
    yield 1	;②
    print 'second'	;③
    yield 2	;④
    print 'third'	;⑤
    yield 3	;⑥
    print 'fourth'	;⑦

for x in fun1():	;⑧
    print x		;⑨

  

很多人都对上面这个代码的执行过程不是很清楚,我最开始也不懂,后来参考一些资料以及自己动手实验,大致知道了这个过程,那么接下来我就说说这个过程:首先之所以fun1可以这种方式迭代,原因是在函数体里面有yield方法,实际上这个fun1已经是一个generator,每一次迭代都是执行当前位置到下一个yield之间的代码,并把这个yield后面跟着的值最为返回传给x,如果你yield后面什么也不跟的话,那么默认是None,下面一步一步分析上面的代码:

  当程序从⑧开始执行的时候第一次对fun1()进行迭代,程序会从①开始知道找到下一个yield也就是②,这中间的代码都会正常执行,所以first会被首先被打印出来,然后程序会在②处暂停,并把yield 1中的1传给本次迭代中的x,然后回去执行⑨,也就会把1(x等于1)输出出来,然后转回⑧开始第二次迭代,第二次迭代会从上一次迭代暂定的地方的下一个语句开始执行也就是③,直到遇到下一个yield语句,也就是④,这之间的代码都会执行,所以会打印出second,然后程序会再一次从这里暂停把④中yield后面的2传给本次迭代的x转到⑨,就会把2(x=2)输出出来,然后程序回到⑧开始第三次迭代,这次迭代会从刚才暂停的yield语句下面那行执行也就是⑤开始,直到遇到下一个yield语句也就是⑥,中间的语句都会执行,所以会打印出来third然后在⑥处暂停,并把⑥中yield后面的3传给本次迭代的x,然后转到⑨打印出3(x=3),然后再条到⑧开始第一次迭代(最后一次),程序从⑦开始执行直到找到下一个yield但是问题就来了,大家发现后面已经没有yield,那么这次迭代就会把⑦以及这之后的所有语句,然后会抛出一StopIteration异常,但是你在运行上面的代码并不会发现抛出异常,这其实是for替我们掩盖了而已(可以理解成for里面加了一个try…except…),他实际上就是通过StopIteration异常来结束迭代的。

  

  通过上面两个程序大家应该对yield的工作过程有所了解了吧,那么还有一个东西需要说明一下,就是send函数,其实我们不仅可以从generator(这里指的就是含有yield的函数,下同)得到它yield的值,还可以给yield传值,send其实和next完成的工作基本都是一样的,只是send多了一个给他当前(这里的当前可以理解成上一次迭代时候暂停的地方)访问的yield表达式传递值,你在generator里面可以用赋值来接收send传进来的值。我们结合一个程序来看看:

def fun2():
    print 'first'    
    yield 1	;①
     print 'second'	
    m=yield 2	;②
    print m	;③
    print 'third'	;
    yield 3	;④
print 'fourth'	
yield 4

h=fun2()
h.next()	  ;⑤
h.next()	  ;⑥
h.send("hello world!");⑦
h.next()    ;⑧

 

结果是:

first

second

hello world!

third

fourth

  程序从⑤开始迭代,第一次迭代的时候fun2运行到①处就暂停,打印内容和上个程序一个道理就不罗嗦了,我们继续从⑥开始迭代,从fun2的①处运行到下一个yield也就是②暂停,回到主程序到⑦,这次迭代是用send所以会给generator传值,从fun2的②处(注意这里的②就是当前的yield,也就是上一次迭代暂停的地方,如果要是用send传值的话也就是给②这个式子传)开始,这里用m接收send传来的值,然后在③处输出出来(hello world!),继续执行直到下一个yield④,回到主程序执行⑧,从④后面执行直到下一个yield,程序结束。

  请大家认真理解一下就会发现两者的异同点了!

  就写这么多了,这是第二篇文章,笔者也是刚刚接触Python所以,难免有所瑕疵和不足,希望大家多多包涵,多多指点。觉的我说的哪里不对,请评论告知,谢谢!

  如需转载,希望大家注明原文出处链接以及说明作者Esfog,谢谢,分享是一种快乐!

http://www.cnblogs.com/Esfog/archive/2012/04/25/2470339.html