python生成器

一 前言

环境: python3.10 win10

二 生成器

1 关于生成器

先看一个例子
image

    定义了一个函数,当我们运行该函数时,并未像普通函数那样执行函数体内的代码
    从其中的英文可知,执行函数得到了一个生成器对象,这个生成器对象也叫做generator iterator(生成器迭代器),generator iterator也属于前面说的迭代器,通常也叫做生成器(generator)

    像这种包含关键字yield的函数,严格来说叫做生成器函数,

    前面的迭代器介绍过,可用next()不断返回迭代器中的元素或者用在for循环中,生成器对象既然属于迭代器,这里也尝试一下
image

    可以看到,每执行一次next(),就可以执行函数内的代码到遇到关键字yield处暂停,并返回yield value中的value作为next()的返回值,再次执行时,会从上次暂停处继续执行到下一个yield处暂停,如此循环,一直到最后没有yield时引发StopIteration异常(就像迭代器一样,一直调用next(),最后没有可返回的数据时引发StopIteration异常)

执行生成器时除了可以使用next(generator),也可以使用generator.send(xxxx)
image

    如上,第一次执行生成器时,如采用方法send(),则必须传递参数None
    如果不是第一次(即恢复执行生成器时),则可以在方法send(value)中添加参数value,value将传进生成器内作为yield 表达式的值,看一个例子
image

    如上,利用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()那一样得到下一个值作为结果
        如果生成器引发了任何其他异常,它将被传播给调用方

image
    如上,我们传进去一个异常ZeroDivisionError后,异常被except 语句捕获,excep与finally中 都没有yield关键字能产生下一个值,所以在finally语句后引发了异常StopIteration,该异常被传递给调用方,即throw()方法的执行处

image
    如上,和上面一个例子的异常捕获稍微有点区别。在死循环里面放置了一个异常捕获的try语句。
这意味着只要没有异常或者符合捕获条件的异常,该循环依然可以继续执行下去

    上面利用throw()方法传进去异常ZeroDivisionError,这个异常类属于Exception的子类,所以执行了Exception中的代码,然后继续执行循环,遇到yield产生了生成器的下一个值,所以没有引发上一个例子的异常StopIteration,继续执行循环

    后面执行close()方法传递进去一个GeneratorExit异常,然后继续执行,由于该异常不属于Exception类型,所以就跳出了死循环,将异常传给了外面的try语句,然后执行了finally。
这种异常不会显示异常信息,所以就没像上个例子那样在后面显示异常信息

image
    如上,这个例子利用except BaseException 捕获到了传进去的GeneratorExit,并在后面的执行中遇到了yield,也就是生成器产生了下一个值,所以这里引发了异常RuntimeError
    但这里没有像throw()那样,把产生的下一个值返回作为close()的结果

四 生成器例子

image
执行结果
image

posted @ 2024-07-30 23:21  工作手记  阅读(10)  评论(0编辑  收藏  举报