在上下文管理器中捕获异常__enter __()

tMC*_*tMC 23 python exception with-statement contextmanager python-2.7

__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__
>>> 
Run Code Online (Sandbox Code Playgroud)

编辑

这是我能得到的尽可能接近......

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
Run Code Online (Sandbox Code Playgroud)

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

Lau*_*low 20

像这样:

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
Run Code Online (Sandbox Code Playgroud)

为了让程序继续以愉快的方式继续而不执行上下文块,您需要检查上下文块中的上下文对象,并且只有__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"
Run Code Online (Sandbox Code Playgroud)

据我所知,你不能完全跳过with-block.请注意,此上下文现在吞下了其中的所有异常.如果您不希望吞下例外,如果__enter__成功,检查self.enter_ok__exit__return False,如果它是True.

  • 如果目标是执行 __exit__ 但不执行 with content,那么您是否只需要执行 `self.__exit__(*sys.exc_info())`,然后无论 __exit__ 返回值如何,都会`raise` 原始异常?我错过了什么吗? (3认同)

Ign*_*ams 8

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


Mar*_*ito 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")
Run Code Online (Sandbox Code Playgroud)

输出:

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

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

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

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

  • 这看起来有点像反模式,因为它允许在不实际使用上下文管理器的情况下使用资源。在许多情况下,这可能会导致编写的代码无法进行正确的清理,而这正是提供上下文管理器的目的。在“__enter__”+“__exit__”的执行确实是完全可选的情况下,该方法可能没问题。诚然,该模式在标准库中经常使用,但这并没有使它变得更好;)。 (2认同)