python yield使用浅析
摘自:https://www.ibm.com/developerworks/cn/opensource/os-cn-python-yield/
初学python的人经常会发现python函数中使用了很多yield关键字,然而,带有yield关键字的函数的执行流程和普通函数的执行流程不同,yield带来了什么作用,为什么要设计yield?本文将由浅入深地讲解yield的概念和用法,帮助读者体会yield在python中的简单而强大的功能
您可能听说过,带有yield的函数在python中称为generator(生成器),何谓生成器?先抛开generator,以一个常见的编程题目来展示yield的概念。
如何生成斐波那契数列
版本1(直接输出斐波那契数列)
斐波那契额数列的概念搭建应该比较清晰,相信大家能共很轻易的写出如下的算法来计算斐波那契数列:
执行fab(5),我们会的到如下的结果:
结果没有问题,但是有经验的开发者会指出,直接在fab函数中 print打印出结果可复用性较差,因为fab返回的结果是None,其他函数无法获取该函数生成的斐波那契数列。所以要提高该函数的可复用性,最好不要直接打印出数列,而是返回一个list
版本二(返回list)
|
|
使用如下方式打印出斐波那契数列:
上述版本获取了可复用性的要求,但是该函数在运行的过程中占用的内存会随着参数max的增大而增大,如果要控制内存占用,最好不要用list来保存中介按结果,而是通过iterable对象来迭代。例如在python2.x中
前者会生成一个长度为100的list,而后者则不会生成一个100的list,而是在每次迭代中返回下一个数值,内存占用空间很小。因为xrange不返回list,而返回一个iterable的意向,利用iterable我们可以吧fab函数写成一个支持iterable的class
版本三(实现支持iterable的对象)
|
|
Fab函数通过next不断返回数列的下一个数,内存占用始终为常数
上述代码虽然实现了我们版本二的要求,但是代码远远没有第一个版本简洁。如果想要保持第一版的简洁,这个时候就要用上yield
版本四
|
|
第四个版本和第一个版本相比仅仅把print b该成了yield b,就在保持简洁性的同时获得了iterable的效果。调用第四个版本和第二个版本的fab完全一致:
简单的将,yield的作用就是把一个函数变成了一个generator,带有yield的函数不在是一个普通函数,python解释器会将其视为一个generator,调用fab(5)不会执行fab函数,而是返回一个iterable对象。在for循环执行的时候,每次循环都会执行fab内部的代码,执行到yield b的时候,fab就返回一个迭代之,下次迭代时,代码从yieldb 的下一条语句执行,而函数的本地变量看起来和上次中断执行前是完全一样的,于是函数继续执行,直到再次遇到yield
也可以调用fab(5)的next()方法进行回去每次计算的值。
yield函数的执行流程
|
|
当函数执行结束的时候,generator自动自动抛出StopIteration的异常,表示迭代的结束,而在for循环中,我们不需要手动的进行处理异常,循环会自动的正常结束。
一个带有yield的函数就是一盒generator,它和普通的函数不同,声称一个generator看起来想函数调用,但是部执行任何函数代码,直到对其调用next()(注意在for循环中会自动调用next)才开始执行。虽然执行流程和普通函数一样,但是每执行到一个yield语句,就会中断,并返回一个迭代值,下次执行的时候从yield的下一个语句开始执行。看起来像是一个函数在正常执行的过程中被yield中断了数次,每次中断都会通过yield返回当前迭代器的值。
yield的好处显而易见,把一个函数该写成generator就获得了迭代能力,比起在类的实例中保存状态计算下一个next的值,更加使代码清洁,而且执行流程非常清晰
判断是否为generator
方法是使用isgeneratorfunction来进行判断
注意fab不可迭代,而fab(5)可迭代
return的作用
在一个generator function中,若函数中没有return语句,默认为函数执行到函数结尾,而如果中间遇到return语句,则直接判处StopIteration异常,结束迭代。
使用yield进行读取文件的例子
上述当文件读取完毕的时候奖直接返回StopIteration异常,结束迭代。