我有以下mcve:
CREATE PUBLIC DATABASE LINK MONITORING_CONFIGURATION
USING 'MONITORING_CONFIGURATION';
以下是样本使用:
import logging
class MyGenIt(object):
def __init__(self, name, content):
self.name = name
self.content = content
def __iter__(self):
with self:
for o in self.content:
yield o
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
if exc_type:
logging.error("Aborted %s", self,
exc_info=(exc_type, exc_value, traceback))
我希望for x in MyGenIt("foo",range(10)):
if x == 5:
raise ValueError("got 5")
报告logging.error
,而是报告ValueError
:
GeneratorExit
当我在ERROR:root:Aborted <__main__.MyGenIt object at 0x10ca8e350>
Traceback (most recent call last):
File "<stdin>", line 8, in __iter__
GeneratorExit
中抓住GeneratorExit
时:
__iter__
没有记录任何内容(当然),因为def __iter__(self):
with self:
try:
for o in self.content:
yield o
except GeneratorExit:
return
是使用__exit__
调用的。
exc_type=None
中看到GeneratorExit
而不是ValueError
?__exit__
中的ValueError
?答案 0 :(得分:3)
基本问题是您尝试在生成器中使用with
语句来捕获在生成器外部引发的异常。您无法让__iter__
看到ValueError,因为__iter__
在引发ValueError时没有执行。
当生成器本身被删除时会引发GeneratorExit异常,这在垃圾回收时会发生。一旦发生异常,for
循环就会终止;由于对生成器的唯一引用(通过调用__iter__
获得的对象)位于循环表达式中,因此终止循环将删除对迭代器的唯一引用,并使其可用于垃圾回收。看来这里立即被垃圾收集,这意味着GeneratorExit异常发生在之间引发ValueError和将ValueError传播到封闭代码。 GeneratorExit通常完全在内部处理;你只是看到它,因为你的with
语句在发生器本身内。
换句话说,流程是这样的:
for
循环退出
.close()
称为在上下文管理器看到GeneratorExit之后,才会发生最后一步。当我运行你的代码时,我看到在打印日志消息后引发了的ValueError。
你可以看到垃圾收集正在工作,因为如果你创建另一个对迭代器本身的引用,它将使迭代器保持活动状态,因此它不会被垃圾收集,因此不会发生GeneratorExit。也就是说,这“有效”:
it = iter(MyGenIt("foo",range(10)))
for x in it:
if x == 5:
raise ValueError("got 5")
结果是ValueError传播并可见;没有发生GeneratorExit,也没有记录任何内容。你似乎认为GeneratorExit以某种方式“掩盖”了你的ValueError,但事实并非如此;它只是通过不保留对迭代器的任何其他引用而引入的工件。 GeneratorExit在您的示例中立即发生的事实甚至不能保证行为;迭代器可能在将来某个未知时间之前不会被垃圾收集,然后将记录GeneratorExit。
关于“我为什么看到GeneratorExit”这个更大的问题,答案是这是生成器函数中实际发生的唯一例外。 ValueError发生在生成器外部,因此生成器无法捕获它。这意味着您的代码无法以您希望的方式运行。您的with
语句 in 生成器函数。因此,它只能捕获从生成器中产生项目的过程中发生的异常;生成器不知道它前进的时间之间会发生什么。但是你的ValueError是在生成器内容的循环体中引发的。此时发电机没有执行;它只是坐在那里暂停。
您不能在生成器中使用with
语句来神奇地捕获在迭代生成器的代码中发生的异常。生成器不“知道”迭代它的代码,也不能处理那里发生的异常。如果要捕获循环体内的异常,则需要一个单独的with
语句来封闭循环本身。
答案 1 :(得分:3)
只需快速说明您可以“将上下文管理器带出”生成器,并且只需更改3行即可:
import logging
class MyGenIt(object):
def __init__(self, name, content):
self.name = name
self.content = content
def __iter__(self):
for o in self.content:
yield o
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
if exc_type:
logging.error("Aborted %s", self,
exc_info=(exc_type, exc_value, traceback))
with MyGenIt("foo", range(10)) as gen:
for x in gen:
if x == 5:
raise ValueError("got 5")
一个也可以充当迭代器的上下文管理器 - 并且会捕获像ValueError一样的调用者代码异常。
答案 2 :(得分:1)
只要生成器或协同程序关闭,就会引发GeneratorExit
。即使没有上下文管理器,我们也可以使用简单的生成器函数复制确切的条件,该函数在错误时打印出异常信息(进一步减少提供的代码以准确显示生成异常的方式和位置)。
import sys
def dummy_gen():
for idx in range(5):
try:
yield idx
except:
print(sys.exc_info())
raise
for i in dummy_gen():
raise ValueError('foo')
用法:
(<class 'GeneratorExit'>, GeneratorExit(), <traceback object at 0x7f96b26b4cc8>)
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
ValueError: foo
注意,还有一个异常是在内部引发生成器本身,正如所指出的那样except
块被执行了。请注意,在print语句之后该异常也是raise
',但请注意它实际上并未在任何地方显示,因为它是在内部处理的。
我们也可以滥用这个事实,看看我们是否可以通过吞下GeneratorExit
异常来操纵流程,看看会发生什么。这可以通过删除raise
函数中的dummy_gen
语句来获得以下输出:
(<class 'GeneratorExit'>, GeneratorExit(), <traceback object at 0x7fd1f0438dc8>)
Exception ignored in: <generator object dummy_gen at 0x7fd1f0436518>
RuntimeError: generator ignored GeneratorExit
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
ValueError: foo
请注意引发的内部RuntimeError
是如何抱怨生成器忽略GeneratorExit
函数的。因此,我们可以清楚地看到,这个异常是由生成器本身在生成器函数内生成的,而ValueError
是在之外引发的,该范围从不存在 inside < / em>生成器功能。
由于上下文管理器将按原样捕获所有异常,并且上下文管理器内部生成器函数,因此在其中引发的任何异常都将按原样传递给__exit__
。请考虑以下事项:
class Context(object):
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
if exc_type:
logging.error("Aborted %s", self,
exc_info=(exc_type, exc_value, traceback))
将dummy_gen
修改为以下内容:
def dummy_gen():
with Context():
for idx in range(5):
try:
yield idx
except:
print(sys.exc_info())
raise
运行生成的代码:
(<class 'GeneratorExit'>, GeneratorExit(), <traceback object at 0x7f44b8fb8908>)
ERROR:root:Aborted <__main__.Context object at 0x7f44b9032d30>
Traceback (most recent call last):
File "foo.py", line 26, in dummy_gen
yield idx
GeneratorExit
Traceback (most recent call last):
File "foo.py", line 41, in <module>
raise ValueError('foo')
ValueError: foo
引发的GeneratorExit
现在呈现给上下文管理器,因为这是已定义的行为。