为什么 GeneratorExit 是由 yield 和 exception 的组合引发的,抑制 ContextDecorator?

why is GeneratorExit raised by this combination of yield & exception suppressing ContextDecorator?

提问人:zsepi 提问时间:11/10/2023 最后编辑:zsepi 更新时间:11/15/2023 访问量:41

问:

我的目标是,而不是写作

for item in items:
    with MyCD():
       ... # processing logic

我想要一行

    for item in MyCD.yield_each_item_wrapped(items):
        ... # processing logic

但是,尽管我通过错误返回来抑制错误,但在引发错误的项目之后,它会立即获得 .ContextDecoratorTrue__exit__yieldGeneratorError

因此,尽管已经处理了引发的异常,但似乎调用了生成器。.close()

我正在运行 python 3.11.4

我本来希望继续处理剩下的两个项目,因为我故意禁止了引发的异常,即:如下面的代码片段所示

def yield2(items):
    for i, item in enumerate(items):
        with MyCD():
            if item:
                raise Exception("my error")
            yield i  # yield success


if __name__ == "__main__":
    raising_flags = [True, False, False, True]
    expected = [1, 2]
    actual = [i for i in yield2(raising_flags)]
    assert expected == actual, (expected, actual)

import traceback
from contextlib import ContextDecorator


class MyCD(ContextDecorator):
    @classmethod
    def yield_each_item_wrapped(cls, items, **kwargs):
        for i, item in enumerate(items):
            print(f"yield_each_item_wrapped {i=}")
            print(f"yield_each_item_wrapped {item=}")
            with cls(**kwargs):
                yield item

    def __enter__(self):
        print("__enter__")

    def __exit__(self, exc_type, exc, exc_tb):
        print("__exit__")
        if exc:
            traceback.print_exc()
            return True  # do not propagate


if __name__ == "__main__":
    raising_flags = [True, False, False, True]
    expected = [1, 2]
    actual = []
    for i, raising in enumerate(MyCD.yield_each_item_wrapped(raising_flags)):
        if raising:
            raise Exception("my error")
        actual.append(i)
    assert expected == actual, (expected, actual)

生产

yield_each_item_wrapped i=0
yield_each_item_wrapped item=True
__enter__
__exit__
Traceback (most recent call last):
  File "x.py", line 12, in yield_each_item_wrapped
    yield item
GeneratorExit
yield_each_item_wrapped i=1
yield_each_item_wrapped item=False
__enter__
Exception ignored in: <generator object MyCD.yield_each_item_wrapped at 0x7f42e625aac0>
Traceback (most recent call last):
  File "x.py", line 30, in <module>
    raise Exception("my error")
RuntimeError: generator ignored GeneratorExit
Traceback (most recent call last):
  File "x.py", line 30, in <module>
    raise Exception("my error")
Exception: my error
Python 生成器 yield contextmanager

评论

0赞 Woodford 11/11/2023
把它变成一条线能解决一个真正的问题,还是只是一个学术问题?/ 版本更有效地表达了您的意图。forwith
0赞 zsepi 11/11/2023
所讨论的上下文装饰器是一个集中式错误处理逻辑,因此在任何地方一致且轻松地使用它非常重要。有相当多的循环,有时是嵌套的。如果我不能把它放在一行中,我需要为每个行嵌套两个级别,这很容易出错(想想复制),更不用说人们只是回退到编写一个 for 循环而不关心错误处理。当然,我们可以在 PR 审查中抓住它,但这是人为的,因此容易出错 另外,我真的很讨厌当我不明白 python 的作用时
1赞 Woodford 11/11/2023
这回答了你的问题吗?GeneratorExit 在 Python 生成器中。从本质上讲,生成器的方法总是被调用的。.close
0赞 zsepi 11/13/2023
没有。我的问题是我希望我的代码具有与以下示例相同的行为(我将此代码段添加到主要问题中): ''' def yield2(items): for i, item in enumerate(items): with MyCD(): if item: raise Exception(“my error”) yield i # yield success if name == “main”: raising_flags = [True, False, False, True] expected = [1, 2] 实际 = [i for i in yield2(raising_flags)] 断言 预期 == 实际, (预期, 实际) '''
0赞 Woodford 11/14/2023
我不会尝试破译您粘贴到评论中的代码,而是您问题的答案:有没有办法避免这种情况?[.close 被调用]不是。链接问题中的答案解释了原因。

答: 暂无答案