python - 为什么这个上下文管理器的行为与字典理解不同?

标签 python python-2.7 exception generator

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

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

奇怪的是,副作用发生在我的脚本“完成”之后。这意味着如果用户正在使用字典理解,则他们不能使用我的上下文装饰器。

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

为什么会这样?

最佳答案

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

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 子句在生成器-迭代器关闭之前不会执行,无论是显式关闭还是作为垃圾收集(refcount 或循环)的结果。似乎 Python 2 列表理解和 Python 3 在垃圾收集 iterable 方面更有效。

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

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) 意味着异常状态在从处理程序 block 退出时被清除,因此 sys.exc_clear() 不是必需的(实际上不存在)。

关于python - 为什么这个上下文管理器的行为与字典理解不同?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24507404/

相关文章:

c++ - 如何使 Visual Studio 2013 显示未处理的异常消息?

java - 异常问题

python - 使用 clang python 绑定(bind)获取二进制操作代码

python-2.7 - 如何在pandas中获取整个数据帧的最大值

python - 如何从 R reticulate 调用 Python 方法

python - TKinter 选项菜单 : How to get the selected choice?

python - 类型错误 : can't convert expression to float

oracle - 嵌套 block 对 PL/SQL 过程中的性能有影响吗?

python - 如何在 password_reset_complete Django 中重定向登录链接

python - 在 MacOS 10.9.5 上安装 Psycopg2 时出错