最近在StackOverflow上看的问题回答比较多,今天这篇是关于介绍Python生成器的相关思想以及其中关键字yield用法的翻译内容。
为了搞清楚yield是用来做什么的,你首先得知道Python中生成器的相关概念,而为了了解生成器的相关概念,你需要知道什么是迭代器。
本篇中的Python除了特殊说明外都是Python 2.x(所有将print当做函数调用如print(xxx)的地方实际应该为print xxx,由于笔者的疏忽,请读者注意)
迭代器
当你创建一个了列表,你可以逐个遍历列表中的元素,而这个过程便叫做迭代:
而mylist是一个可迭代对象。当你使用列表推导式的时候,创建了一个列表,他也是可迭代对象:
所有能够接受for...in...操作的对象都是可迭代对象,如列表、字符串、文件等。这些可迭代对象用起来都十分顺手因为你可以按照你的想法去访问它们,但是你把所有数据都保存在了内存中,而当你有大量数据的时候这可能并不是你想要的结果。
生成器
生成器也是迭代器,但是你只能对它们进行一次迭代,原因在于它们并没有将所有数据存储在内存中,而是即时生成这些数据:
这一段代码和上面那段很相似,唯一不同的地方是使用了()代替[]。但是,这样的后果是你无法对mygenerator进行第二次for i in mygenerator,因为生成器只能被使用一次:它首先计算出结果0,然后忘记它再计算出1,最后是4,一个接一个。
Yield
yield是一个用法跟return很相似的关键字,不同在于函数返回的是一个生成器。
这是一个没有什么用的例子,但是用来让你了解当你知道你的函数会返回一个只会被遍历1次的巨大数据集合该怎么做的时候十分方便。为了掌握yield,你必须了解当你调用这个函数的时候,你在函数体中写的代码并没有被执行,而是只返回了一个生成器对象,这个需要特别注意。然后,你的代码将会在每次for使用这个生成器的时候被执行。最后,最困难的部分:
生成器在函数执行了却没有到达yield的时候将被认为是空的,原因在于循环到达了终点,或者不再满足if/else条件。
处理生成器耗尽
考虑以下代码:
先看生成器的next方法,它用来执行代码并从生成器中获取下一个元素(在Python 3.x中生成器已经没有next方法,而是使用next(iterator)代替)。在crisis未被置为True的时候,create_atm函数中的while
循环可以看做是无尽的,当crisis为True的时候,跳出了while循环,所有迭代器将会到达函数尾部,此时再次访问next将会抛出StopIteration异常,而此时就算将crisis设置为False,这些生成器仍然处在函数尾部,访问会继续抛出StopIteration异常。
将以上例子用来控制访问资源等用途的时候十分有用。
itertools,你的好朋友
itertools模块包含了许多用来操作可迭代对象的函数。想复制一个生成器?向连接两个生成器?想把多个值组合到一个嵌套列表里面?使用map/zip而不用重新创建一个列表?那么就:import itertools吧。
让我们来看看四匹马赛跑可能的到达结果:
迭代的内部机理
迭代是一个依赖于可迭代对象(需要实现__iter__()方法)和迭代器(需要实现__next__()方法)的过程。
总结
yield语句将你的函数转化成一个能够生成一种能够包装你原函数体的名叫生成器的特殊对象的工厂。当生成器被迭代,它将会起始位置开始执行函数一直到到达下一个yield,然后挂起执行,计算返回传递给yield的值,它将会在每次迭代的时候重复这个过程直到函数执行到达函数的尾部,举例来说:
输出结果为:
这种效果的产生是由于在循环中使用了可以产生序列的生成器,生成器在每次循环时执行代码到下一个yield,并计算返回结果,这样生成器即时生成了一个列表,这对于特别是大型计算来说内存节省十分有效。
假设你想实现自己的可以产生一个可迭代一定范围数的range函数(特指Python 2.x中的range),你可以这样做和使用:
但是这样并不高效,原因1:你创建了一个你只会使用一次的列表;原因2:这段代码实际上循环了两次。
由于Guido和他的团队很慷慨地开发了生成器因此我们可以这样做:
现在,每次对生成器迭代将会调用next()来执行函数体直到到达yield语句,然后停止执行,并计算返回结果,或者是到达函数体尾部。在这种情况下,第一次的调用next()将会执行到yield n并返回n
,下一次的next()将会执行自增操作,然后回到while的判断,如果满足条件,则再一次停止并返回n,它将会以这种方式执行一直到不满足while条件,使得生成器到达函数体尾部。
本文原文地址:http://youchen.me/2017/02/10/Python-What-does-yield-do/