Python生成器中的GeneratorExit

时间:2015-06-16 08:16:08

标签: python generator

我写了一个关于Python生成器的测试程序。但是我得到了一个不期望的错误。而且我不知道如何解释它。我来告诉你代码:

def countdown(n):
    logging.debug("Counting down")
    while n > 0:
        try:
            yield n
        except GeneratorExit:
            logging.error("GeneratorExit")
        n -= 1


if __name__ == '__main__':
    c = countdown(10)
    logging.debug("value: %d", c.next())

我认为应该没有任何问题。但输出是:

# ./test.py
[2015/06/16 04:10:49] DEBUG    - Counting down     
[2015/06/16 04:10:49] DEBUG    - value: 10 
[2015/06/16 04:10:49] ERROR    - GeneratorExit
Exception RuntimeError: 'generator ignored GeneratorExit' in <generator object countdown at 0x7f9934407640> ignored

为什么最后一行出错。我不知道为什么我触发了GeneratorExit异常。我错过了什么东西生成器吗?我还将代码输入到交互式python shell中,一切正常。怎么会发生这种情况?

3 个答案:

答案 0 :(得分:22)

假设您有以下生成器:

def gen():
    with open('important_file') as f:
        for line in f:
            yield line

并且你next一次并扔掉它:

g = gen()
next(g)
del g

生成器的控制流永远不会离开with块,因此文件不会关闭。为了防止这种情况,当一个生成器被垃圾收集时,Python会调用它的close方法,该方法在生成器上次GeneratorExit编辑的位置引发yield异常。此异常旨在触发任何无法运行的finally块或上下文管理器__exit__

当你抓住GeneratorExit并继续前进时,Python会发现生成器没有正确退出。由于这可能表明资源未正确发布,因此Python将其报告为RuntimeError。

答案 1 :(得分:6)

当在程序结束时对生成器对象进行垃圾收集时,会调用其close()方法,这会在生成器内引发GeneratorExit异常。通常这不会被捕获并导致发电机退出。

由于您捕获异常并继续产生另一个值,因此会导致RuntimeError。如果你遇到GeneratorExit异常,你需要重新加注它,或退出函数而不产生其他任何东西。

答案 2 :(得分:0)

也许是因为您的yield值n在try块内,该块总是返回新的n引用,这使最后一个n值被自动垃圾收集。它也在PEP 342中声明:

  

“添加支持以确保在垃圾回收生成器迭代器时调用close()”

     

“允许在try / finally块中使用yield,因为垃圾回收或显式的close()调用现在将允许执行finally子句。”

由于generator中的close方法等效于抛出GeneratorExit并被您的异常捕获,因此将执行logging.error('GeneratorExit')表达式。

引发“ RunTimeError”,因为生成器产生了下一个值n(9),它在python文档https://docs.python.org/3.6/reference/expressions.html#generator-iterator-methods中处于状态:

  

“在生成器函数处于的位置处引发GeneratorExit   停顿了。如果generator函数然后正常退出,则已经   关闭,或引发GeneratorExit(通过不捕获异常),关闭   返回其调用者。 如果生成器产生一个值,则   引发RuntimeError 。如果生成器引发任何其他异常,   它被传播给调用者。如果生成器,close()不执行任何操作   由于异常或正常退出而已经退出”

可能是这样的代码:

#pygen.py

import sys
import logging
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s \
        %(levelname)s - %(message)s', datefmt='[%Y/%m/%d %H:%M:%S]')

def genwrapper(func):
    #makes gen wrapper
    #which automatically send(None) to generator
    def wrapper(n=None):
        f = func(n)
        f.send(None)
        return f
    return wrapper

@genwrapper
def countdown(n=None):
    logging.debug('Counting Down')
    while True:
        try:
            n = yield(n)
        except GeneratorExit as e:
            logging.error('GeneratorExit')
            raise e

if __name__ == '__main__':
    n = int(sys.argv[1])
    c = countdown() #avoid function call in loop block (avoid new reference to c)
     while n > 0:
         a = c.send(n)
         logging.debug('Value: %d', a)
         n -= 1

然后在您的终端中

guest@xxxxpc:~$ python pygen.py 5

将导致:

[2018/12/13 16:50:45]         DEBUG - Counting Down
[2018/12/13 16:50:45]         DEBUG - Value: 5
[2018/12/13 16:50:45]         DEBUG - Value: 4
[2018/12/13 16:50:45]         DEBUG - Value: 3
[2018/12/13 16:50:45]         DEBUG - Value: 2
[2018/12/13 16:50:45]         DEBUG - Value: 1
[2018/12/13 16:50:45]         ERROR - GeneratorExit

对不起,我的英语不好,或者我的建议还不够清楚,谢谢