为什么此上下文管理器的行为与dict理解不同? [英] Why does this contextmanager behave differently with dict comprehensions?
问题描述
我有一个上下文装饰器,它在完成时有副作用.我已经注意到,如果我使用dict理解,不会出现副作用.
I have a context decorator that has side effects when it's done. I've noticed that the side effects don't occur if I use a dict comprehension.
from contextlib import contextmanager
import traceback
import sys
accumulated = []
@contextmanager
def accumulate(s):
try:
yield
finally:
print("Appending %r to accumulated" % s)
accumulated.append(s)
def iterate_and_accumulate(iterable):
for item in iterable:
with accumulate(item):
yield item
def boom_unless_zero(i):
if i > 0:
raise RuntimeError("Boom!")
try:
{i: boom_unless_zero(i) for i in iterate_and_accumulate([0, 1])}
except:
traceback.print_exc()
print(accumulated)
print('\n=====\n')
try:
{i: boom_unless_zero(i) for i in iterate_and_accumulate([0, 1])}
except:
traceback.print_exc()
print(accumulated)
print('Finished!')
输出:
$ python2 boom3.py
Appending 0 to accumulated
Traceback (most recent call last):
File "boom3.py", line 25, in <module>
{i: boom_unless_zero(i) for i in iterate_and_accumulate([0, 1])}
File "boom3.py", line 25, in <dictcomp>
{i: boom_unless_zero(i) for i in iterate_and_accumulate([0, 1])}
File "boom3.py", line 22, in boom_unless_zero
raise RuntimeError("Boom!")
RuntimeError: Boom!
[0]
=====
Appending 0 to accumulated
Appending 1 to accumulated
Traceback (most recent call last):
File "boom3.py", line 34, in <module>
{i: boom_unless_zero(i) for i in iterate_and_accumulate([0, 1])}
File "boom3.py", line 34, in <dictcomp>
{i: boom_unless_zero(i) for i in iterate_and_accumulate([0, 1])}
File "boom3.py", line 22, in boom_unless_zero
raise RuntimeError("Boom!")
RuntimeError: Boom!
[0, 0, 1]
Finished!
Appending 1 to accumulated
奇怪的是,副作用在我的脚本完成"之后发生.这意味着用户使用dict理解时将无法使用我的contextdecorator.
It's bizarre that the side effect occurs after my script is 'finished'. It means users can't use my contextdecorator if they're using dict comprehensions.
我注意到,此行为在Python 3上消失了,如果我在iterate_and_accumulate([0,1])]中为我编写 [boom_unless_zero(i)],也不会发生该行为
而不是字典理解.
I've noticed that this behaviour disappears on Python 3, and the behaviour also doesn't occur if I write [boom_unless_zero(i) for i in iterate_and_accumulate([0, 1])]
instead of the dict comprehension.
为什么会这样?
推荐答案
来自 从Python 2.5版本开始,现在可以在try ... finally构造的try子句中使用yield语句.如果生成器在完成之前没有恢复(通过达到零引用计数或被垃圾回收),则将调用generator-iterator的close()方法,从而允许任何未决的finally子句执行.
As of Python version 2.5, the yield statement is now allowed in the try clause of a try ... finally construct. If the generator is not resumed before it is finalized (by reaching a zero reference count or by being garbage collected), the generator-iterator’s close() method will be called, allowing any pending finally clauses to execute. 换句话说,待生成的迭代器显式关闭或由于垃圾回收(引用计数或循环)而关闭生成器迭代器之前,待执行的finally子句将不会执行.似乎Python 2列表理解和Python 3在垃圾回收可迭代对象方面效率更高. In other words, pending finally clauses will not execute until the generator-iterator is closed, either explicitly or as a result of it being garbage-collected (refcount or cyclic). It seems that Python 2 list comprehensions and Python 3 are more efficient at garbage collecting the iterable. 如果您想明确说明如何关闭生成器迭代器: If you want to be explicit about closing the generator-iterator: 我看了根本的问题;似乎问题在于生成器迭代器处于异常回溯状态,因此另一个解决方法是调用 在Python 3中,词汇异常处理程序系统( http://bugs.python.org/issue3021)表示在从处理程序块退出时清除了异常状态,因此 In Python 3, the lexical exception handler system (http://bugs.python.org/issue3021) means that the exception state is cleared on exit from the handler block, so 这篇关于为什么此上下文管理器的行为与dict理解不同?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!
from contextlib import closing
try:
with closing(iter(iterate_and_accumulate(a))) as it:
{i: boom_unless_zero(i) for i in it}
except:
traceback.print_exc()
print(accumulated)
sys.exc_clear()
:import sys
try:
{i: boom_unless_zero(i) for i in iterate_and_accumulate(a)}
except:
traceback.print_exc()
try:
sys.exc_clear()
except AttributeError:
pass
print(accumulated)
sys.exc_clear()
是不必要的(并且实际上不存在).sys.exc_clear()
is not necessary (and indeed is not present).