我写了一个关于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中,一切正常。怎么会发生这种情况?
答案 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
对不起,我的英语不好,或者我的建议还不够清楚,谢谢