繁体   English   中英

为什么这个上下文管理器与dict理解有不同的表现?

[英]Why does this contextmanager behave differently with dict comprehensions?

我有一个上下文装饰器,它完成后会产生副作用。 我注意到如果我使用字典理解,就不会出现副作用。

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。

我注意到这种行为在Python 3上消失了,如果我[boom_unless_zero(i) for i in iterate_and_accumulate([0, 1])]编写[boom_unless_zero(i) for i in iterate_and_accumulate([0, 1])]而不是dict理解,那么行为也不会发生。

为什么会这样?

来自https://docs.python.org/2/reference/simple_stmts.html#the-yield-statement

从Python 2.5版开始,现在允许在try ... finally结构的try子句中使用yield语句。 如果生成器在最终确定之前没有恢复(通过达到零引用计数或通过垃圾收集),将调用generator-iterator的close()方法,允许执行任何挂起的finally子句。

换句话说,挂起finally子句直到生成器迭代器被关闭时才会执行,无论是显式还是由于它被垃圾收集(引用计数或循环)。 似乎Python 2列表推导和Python 3在垃圾收集迭代方面更有效。

如果你想明确关闭生成器 - 迭代器:

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)

在Python 3中,词法异常处理程序系统( http://bugs.python.org/issue3021 )意味着异常状态在从处理程序块退出时被清除,因此sys.exc_clear()不是必需的(事实上并非如此)当下)。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM