为什么此上下文管理器的行为与dict理解不同? [英] Why does this contextmanager behave differently with dict comprehensions?

查看:49
本文介绍了为什么此上下文管理器的行为与dict理解不同?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个上下文装饰器,它在完成时有副作用.我已经注意到,如果我使用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:

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()是不必要的(并且实际上不存在).

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 sys.exc_clear() is not necessary (and indeed is not present).

这篇关于为什么此上下文管理器的行为与dict理解不同?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆