python生成器
一 前言
环境: python3.10 win10
二 生成器
1 关于生成器
先看一个例子
定义了一个函数,当我们运行该函数时,并未像普通函数那样执行函数体内的代码
从其中的英文可知,执行函数得到了一个生成器对象,这个生成器对象也叫做generator iterator(生成器迭代器),generator iterator也属于前面说的迭代器,通常也叫做生成器(generator)
像这种包含关键字yield的函数,严格来说叫做生成器函数,
前面的迭代器介绍过,可用next()不断返回迭代器中的元素或者用在for循环中,生成器对象既然属于迭代器,这里也尝试一下
可以看到,每执行一次next(),就可以执行函数内的代码到遇到关键字yield处暂停,并返回yield value中的value作为next()的返回值,再次执行时,会从上次暂停处继续执行到下一个yield处暂停,如此循环,一直到最后没有yield时引发StopIteration异常(就像迭代器一样,一直调用next(),最后没有可返回的数据时引发StopIteration异常)
执行生成器时除了可以使用next(generator),也可以使用generator.send(xxxx)
如上,第一次执行生成器时,如采用方法send(),则必须传递参数None
如果不是第一次(即恢复执行生成器时),则可以在方法send(value)中添加参数value,value将传进生成器内作为yield 表达式的值,看一个例子
如上,利用send(None)开始执行生成器时,执行到第一个yield表达式暂停,此时只执行了表达式右边即yield 0 这一部分,左边部分给v的赋值是没有执行的。
第2次执行send(value)时,第一处yield左边部分给v的赋值才开始执行(value的值作为v的值),然后暂停到第2处yield的位置,第2处yield处v的赋值要到下一次执行时间,如此循环
所以照此推理,第一次执行send()时,应该是第0处的yield处的左边部分赋值开始执行,但第0处是不存在的,所以,第一次的参数必须为None即send(None)
这里需要注意的是,yield value中的value是从生成器传到外面作为next()或者send(xxx)的结果,而send(value)中的value是传到生成器里面去替换掉yield xxx作为v的值
2 生成器的一些方法
-
generator__next__:
此方法通常是隐式调用,通过for循环或者内置的next()函数 -
generator.send(value):-
-
generator.throw(type, value, traceback)
在生成器暂停位置(yield处)引发一个异常(传入一个异常到生成器里面去,让生成器带着异常继续执行代码)
若异常发生后,生成器能继续产生下一个值,则将下一个值作为throw()的结果。此时相当于是send(value),只不过value参数是一个异常
若异常发生后,生成器没有继续产生下一个值,则将引发StopIteration异常,从而退出执行
如果生成器函数没有捕获传入的异常,或是引发了另一个异常,则该异常会被传播给调用方即throw()处
type: 一个异常类
value: 可选参数
traceback: 可选参数
- generator.close()
在生成器暂停位置(yield处)引发GeneratorExit从而关闭生成器的执行(同样是传递一个异常到生成器里面去)
注意,GeneratorExit直接继承自 BaseException 而不是 Exception,不会像Exception类异常那样(Exception类异常未被处理时,则会显示该异常信息)
和throw()类似的是,传入该异常后,生成器若没有继续产生下一个值,GeneratorExit只会停止代码的执行并返回到调用方即close()处
传入该异常后,如果生成器产生了下一个值,则将引发异常 RuntimeError,但不会像throw()那一样得到下一个值作为结果
如果生成器引发了任何其他异常,它将被传播给调用方
如上,我们传进去一个异常ZeroDivisionError后,异常被except 语句捕获,excep与finally中 都没有yield关键字能产生下一个值,所以在finally语句后引发了异常StopIteration,该异常被传递给调用方,即throw()方法的执行处
如上,和上面一个例子的异常捕获稍微有点区别。在死循环里面放置了一个异常捕获的try语句。
这意味着只要没有异常或者符合捕获条件的异常,该循环依然可以继续执行下去
上面利用throw()方法传进去异常ZeroDivisionError,这个异常类属于Exception的子类,所以执行了Exception中的代码,然后继续执行循环,遇到yield产生了生成器的下一个值,所以没有引发上一个例子的异常StopIteration,继续执行循环
后面执行close()方法传递进去一个GeneratorExit异常,然后继续执行,由于该异常不属于Exception类型,所以就跳出了死循环,将异常传给了外面的try语句,然后执行了finally。
这种异常不会显示异常信息,所以就没像上个例子那样在后面显示异常信息
如上,这个例子利用except BaseException 捕获到了传进去的GeneratorExit,并在后面的执行中遇到了yield,也就是生成器产生了下一个值,所以这里引发了异常RuntimeError
但这里没有像throw()那样,把产生的下一个值返回作为close()的结果
四 生成器例子
执行结果