Python yield 总结

来源:互联网 发布:欧洲卡车模拟 知乎 编辑:程序博客网 时间:2024/06/07 10:25

对python的yield一直没搞明白,今天心血来潮,看了一下书和网上的博客 。

FIRST

迭代器(Iterator)

为了理解yield是什么,首先要明白生成器(generator)是什么,在讲生成器之前先说说迭代器(iterator),当创建一个列表(list)时,你可以逐个的读取每一项,这就叫做迭代(iteration)。

 mylist = [1, 2, 3] for i in mylist :     print(i) 1 2 3

Mylist就是一个迭代器,不管是使用复杂的表达式列表,还是直接创建一个列表,都是可迭代的对象。

你可以使用“for··· in ···”来操作可迭代对象,如:list,string,files,这些迭代对象非常方便我们使用,因为你可以按照你的意愿进行重复的读取。但是你不得不预先存储所有的元素在内存中,那些对象里有很多元素时,并不是每一项都对你有用。

生成器(Generators)

生成器同样是可迭代对象,但是你只能读取一次,因为它并没有把所有值存放内存中,它动态的生成值:

mygenerator = (x*x for x in range(3))   for i in mygenerator :       print(i)   0   1   4

使用()和[]结果是一样的,但是,第二次执行“ for in mygenerator”不会有任何结果返回,因为它只能使用一次。首先计算0,然后计算1,之后计算4,依次类推。你可以试一下,真的只能运行一次。

Yield

Yield是关键字, 用起来像return,yield在告诉程序,要求函数返回一个生成器。

def createGenerator() :       mylist = range(3)       for i in mylist :           yield i*i   mygenerator = createGenerator() # create a generator   print(mygenerator) # mygenerator is an object!   <generator object createGenerator at 0xb7555c34>   for i in mygenerator:       print(i)   0   1   4

这个示例本身没什么意义,但是它很清晰地说明函数将返回一组仅能读一次的值,要想掌握yield,首先必须理解的是:当你调用生成器函数的时候,如上例中的createGenerator(),程序并不会执行函数体内的代码,它仅仅只是返回生成器对象,这种方式颇为微妙。函数体内的代码只有直到每次循环迭代(for)生成器的时候才会运行。

函数第一次运行时,它会从函数开始处直到碰到yield时,就返回循环的第一个值,然后,交互的运行、返回,直到没有值返回为止。如果函数在运行但是并没有遇到yield,就认为该生成器是空,原因可能是循环终止,或者没有满足任何”if/else”。

AND THEN

接下来我引用另一篇博客关于yield的应用的例子

def h():    print 'To be brave'    yield 5h()

这样执行是没有输出的,因为如前面所言,h()只是调用生成函数,并不会执行函数体内的代码,仅是返回生成体对象。

那怎么执行内部代码呢?继续看

def h():    print 'Wen Chuan'    yield 5    print 'Fighting!'c = h()c.next()

我们通过next()语句让它执行。next()语句将恢复Generator的执行,并直到下一个yield表达式处。c.next()调用后,h()开始执行,直到遇到yield 5,因此输出结果:

Wen Chuan

当我们再次调用c.next()时,会继续执行,直到找到下一个yield表达式。由于后面没有yield了,因此会拋出异常:

Wen ChuanFighting!Traceback (most recent call last):File "/home/evergreen/Codes/yidld.py", line 11, in <module>c.next()StopIteration

了解了next()如何让包含yield的函数执行后,我们再来看另外一个非常重要的函数send(msg)。其实next()和send()在一定意义上作用是相似的,区别是send()可以传递yield表达式的值进去,而next()不能传递特定的值,只能传递None进去。因此,我们可以看做c.next() 和 c.send(None) 作用是一样的。

def h():print 'Wen Chuan',m = yield 5  # Fighting!print md = yield 12print 'We are together!'c = h()c.next()  #相当于c.send(None)c.send('Fighting!')  #(yield 5)表达式被赋予了'Fighting!'

需要提醒的是,第一次调用时,请使用next()语句或是send(None),不能使用send发送一个非None的值,否则会出错的,因为没有yield语句来接收这个值。

我一开始看上面的提醒有点懵逼,为什么一开始不能用send(something),something不等于None,后来我在”python基础教程”中找到了原因:

使用send方法(而不是next方法)只有在生成器挂起之后才有意义,也就是说在yield函数第一次被执行之后,如果在此之前需要给生成器提供更多信息,那么只需使用生成器函数的参数。

如果真想对刚刚启动的生成器使用send方法,那么可以将None作为其参数进行调用。

另外,为什么print m的内容是fighting呢?

在内部挂起生成器,yield作为表达式使用,换句话说,当生成器重新运行的时候,yield方法返回一个值,也就是外部通过send方法发送的值。如果是next方法,那么yield方法返回0。

继续那篇文章的例子

send(msg) 和 next()是有返回值的,它们的返回值很特殊,返回的是下一个yield表达式的参数。比如yield 5,则返回 5 。到这里,是不是明白了一些什么东西?本文第一个例子中,通过for i in alist 遍历 Generator,其实是每次都调用了alist.Next(),而每次alist.Next()的返回值正是yield的参数,即我们开始认为被压进去的东东。我们再延续上面的例子:

def h():    print 'Wen Chuan',    m = yield 5  # Fighting!    print m    d = yield 12    print 'We are together!'c = h()m = c.next()  #m 获取了yield 5 的参数值 5d = c.send('Fighting!')  #d 获取了yield 12 的参数值12print 'We will never forget the date', m, '.', d

输出结果:

Wen Chuan Fighting!We will never forget the date 5 . 12

中断Generator是一个非常灵活的技巧,可以通过throw抛出一个GeneratorExit异常来终止Generator。Close()方法作用是一样的,其实内部它是调用了throw(GeneratorExit)的。我们看:

 def close(self):     try:         self.throw(GeneratorExit)     except (GeneratorExit, StopIteration):         pass     else:         raise RuntimeError("generator ignored GeneratorExit")#Other exceptions are not caught

因此,当我们调用了close()方法后,再调用next()或是send(msg)的话会抛出一个异常:

Traceback (most recent call last):File "/home/evergreen/Codes/yidld.py", line 14, in <module>d = c.send('Fighting!')  #d 获取了yield 12 的参数值12StopIteration

参考文章

1.http://blog.jobbole.com/28506/
2.http://www.jb51.net/article/15717.htm

0 0
原创粉丝点击