29

__exit__()即使有异常,是否可以确保调用该方法__enter__()

>>> class TstContx(object):
...    def __enter__(self):
...        raise Exception('Oops in __enter__')
...
...    def __exit__(self, e_typ, e_val, trcbak):
...        print "This isn't running"
... 
>>> with TstContx():
...     pass
... 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in __enter__
Exception: Oops in __enter__
>>> 

编辑

这是我能得到的最接近的...

class TstContx(object):
    def __enter__(self):
        try:
            # __enter__ code
        except Exception as e
            self.init_exc = e

        return self

    def __exit__(self, e_typ, e_val, trcbak):
        if all((e_typ, e_val, trcbak)):
            raise e_typ, e_val, trcbak

        # __exit__ code


with TstContx() as tc:
    if hasattr(tc, 'init_exc'): raise tc.init_exc

    # code in context

事后看来,上下文管理器可能不是最好的设计决策

4

7 回答 7

27

像这样:

import sys

class Context(object):
    def __enter__(self):
        try:
            raise Exception("Oops in __enter__")
        except:
            # Swallow exception if __exit__ returns a True value
            if self.__exit__(*sys.exc_info()):
                pass
            else:
                raise


    def __exit__(self, e_typ, e_val, trcbak):
        print "Now it's running"


with Context():
    pass

要让程序继续其愉快的方式而不执行上下文块,您需要检查上下文块内的上下文对象,并且只有在__enter__成功时才执行重要的事情。

class Context(object):
    def __init__(self):
        self.enter_ok = True

    def __enter__(self):
        try:
            raise Exception("Oops in __enter__")
        except:
            if self.__exit__(*sys.exc_info()):
                self.enter_ok = False
            else:
                raise
        return self

    def __exit__(self, e_typ, e_val, trcbak):
        print "Now this runs twice"
        return True


with Context() as c:
    if c.enter_ok:
        print "Only runs if enter succeeded"

print "Execution continues"

据我所知,您不能完全跳过 with-block。请注意,此上下文现在吞没了其中的所有异常。如果您不希望在__enter__成功后吞下异常,请签self.enter_ok__exit__并且return False如果它是True.

于 2012-10-25T18:38:44.537 回答
12

不可以。如果有可能发生异常,__enter__()那么您需要自己捕获它并调用包含清理代码的辅助函数。

于 2012-10-25T18:31:19.137 回答
5

我建议您遵循 RAII(资源获取是初始化)并使用上下文的构造函数来执行可能失败的分配。然后你__enter__可以简单地返回 self ,它永远不会引发异常。如果您的构造函数失败,则在进入 with 上下文之前可能会引发异常。

class Foo:
    def __init__(self):
        print("init")
        raise Exception("booh")

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

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("exit")
        return False


with Foo() as f:
    print("within with")

输出:

init
Traceback (most recent call last):
  File "<input>", line 1, in <module>
  ...
    raise Exception("booh")
Exception: booh

编辑: 不幸的是,这种方法仍然允许用户创建“悬空”资源,如果他执行以下操作则不会被清理:

foo = Foo() # this allocates resource without a with context.
raise ValueError("bla") # foo.__exit__() will never be called.

我很好奇这是否可以通过修改类的实现或其他一些禁止在没有上下文的情况下实例化对象的 python 魔法来解决。

于 2019-11-11T17:44:50.570 回答
3

您可以使用contextlib.ExitStack(未​​测试):

with ExitStack() as stack:
    cm = TstContx()
    stack.push(cm) # ensure __exit__ is called
    with ctx:
         stack.pop_all() # __enter__ succeeded, don't call __exit__ callback

或文档中的示例:

stack = ExitStack()
try:
    x = stack.enter_context(cm)
except Exception:
    # handle __enter__ exception
else:
    with stack:
        # Handle normal case

请参阅Python <3.3 上的 contextlib2

于 2012-10-25T18:51:15.940 回答
3

如果不需要继承或复杂的子例程,您可以使用更短的方法:

from contextlib import contextmanager

@contextmanager
def test_cm():
    try:
        # dangerous code
        yield  
    except Exception, err
        pass # do something
于 2012-10-25T18:55:12.300 回答
2
class MyContext:
    def __enter__(self):
        try:
            pass
            # exception-raising code
        except Exception as e:
            self.__exit__(e)

    def __exit__(self, *args):
        # clean up code ...
        if args[0]:
            raise

我已经这样做了。它以错误为参数调用 __exit__()。如果 args[0] 包含错误,它会在执行清理代码后重新引发异常。

于 2016-12-22T12:20:10.863 回答
1

文档包含一个contextlib.ExitStack用于确保清理的示例:

如 文档中所述,如果实施中的后续步骤失败ExitStack.push(),此方法可用于清理已分配的资源。__enter__()

因此,您将ExitStack()用作上下文管理器周围的包装上下文管理TstContx()器:

from contextlib import ExitStack

with ExitStack() as stack:
    ctx = TstContx()
    stack.push(ctx)  # Leaving `stack` now ensures that `ctx.__exit__` gets called.
    with ctx:
        stack.pop_all()  # Since `ctx.__enter__` didn't raise it can handle the cleanup itself.
        ...  # Here goes the body of the actual context manager.
于 2020-11-04T09:57:13.463 回答