搬砖的也能玩Python——进阶篇
2-生成器
回顾
在第一篇进阶篇中,我们了解了迭代器的相关内容,清楚迭代器的两个主要的方法__next__和__iter__,区分迭代器对象和可迭代对象,本篇文章我们将了解一个特殊的迭代器——生成器(generator)。
一、生成器的相关概念
生成器函数
我们在基础篇6中已经了解了函数的概念,而生成器函数只是在函数的基础上,将return关键字改成yield关键字即可,也就是:
def 函数名(参数):
函数体
yield 返回值
yield每次返回一个结果,通过在每个结果之间挂起和继续它们的状态,来自动实现迭代协议,不需要再明确定义__iter__和__next__方法,就可以达到迭代的效果。
生成器
生成器函数返回生成器的迭代器,而这个“生成器的迭代器”就是我们所说的“生成器”,所以说,生成器是一种特殊的迭代器,可以用for循环进行遍历,可以通过next()方法来获取下一个值。
接下来,我们来看一个生成器的简单例子:
在上图的例子中,我们定义了一个生成器函数,调用函数之后,我们使用__next__()方法,来获得每一个值,在上图的基础上,如果我们再加上一句print(g.__next__()),那么就会出现StopIteration的异常。
由于生成器本身也是迭代器,所以我们也可以用for循环来得到里面的值:
二、生成器的执行顺序
在生成器函数中,我们用了yield每次返回一个结果,具体的执行顺序为:通过__next__()方法开始执行,执行到yield就暂停,返回yield后面的内容,再次使用__next__()方法,则接着上一次暂停的语句处继续执行,执行到yield处停止,如果没有再次找到yield的话,就抛出StopIteration异常。
我们通过下面的例子,来清晰的了解生成器的执行顺序:
在上图的例子中,我们在yield语句的前后都加了print方法,来看一下每次执行__next__()方法时,到底执行了哪些内容。首先来看第一次执行后的结果:
第一次执行之后,我们发现确实只执行到了yield这一句就暂停了,我们继续执行第二次__next__()方法,来看看结果:
为了方便区分这两次__next__()方法,我在中间加了一个分割线,这样一看就很明显了,第二次执行__next__()方法时,先接着上一次暂停的yield语句继续执行,先执行了print("yield之后的语句")方法,然后一直执行到yield语句,然后暂停。
我们执行完所有的__next__()方法:
当执行到最后一个__next__()方法时,此时已经结束了while循环,也就无法再找到yield方法,便会抛出StopIteration异常。我们来看一下最终的结果:
三、生成器表达式
生成器表达式可以帮助我们每次获取一个元素,并对该元素进行解析,这样通过生成器一边循环一边计算,可以节省大量的空间。具体的表达式格式如下:
(exprforiter_variniterableifcond_expr)
一个生成器表达式,用圆括号括起来,先迭代iterable中的内容,每一次迭代会判断迭代的内容是否满足cond_expr条件表达式,如果满足,就把内容赋值给iter_var,然后再执行expr这个表达式,expr得到的结果就相当于yield后面的语句。来看下面这个例子:
这就是一个生成器表达式,由于生成器表达式得到的是一个生成器,所以我们可以直接使用__next__()方法或者for循环来进行遍历。
四、yield交互(send、close方法)
在生成器中,我们还有一个有意思的地方,就是yield可以作为一个表达式来赋值,也就是val = yield。那么问题就来了,yield的值从哪里来呢?
这就需要send()方法了,send()方法传入的参数,就是yield的值。
注意
通过前面的学习,我们知道在使用__next__()方法时,执行到yield就暂停,再次执行__next__()时,才能继续执行后面的内容。我们send()方法的大前提就是:生成器必须处于在yield位置暂停的状态,才可以用send()方法给yield传递值。
所以,在使用send()方法给yield传递值之前,必须使用__next__()方法,先让生成器暂停才行,否则会有异常抛出。
我们来看一下send()方法的例子:
如果我们不执行g.__next__()方法,直接执行g.send("abc"),会有如下报错:
通过使用close()方法,可以关闭当前的生成器,当生成器关闭之后,再使用__next__()方法和send()方法,便会抛出StopIteration异常。
探测八个蛋∣跳出手工测试的井
领取专属 10元无门槛券
私享最新 技术干货