即使
__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
事后看来,上下文管理器可能不是最好的设计决策
像这样:
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 块。请注意,此上下文现在包含了其中的“所有”异常。如果您不想在 __enter__
成功时吞下异常,请检查
self.enter_ok
中的 __exit__
和 return False
(如果是 True
)。__enter__()
中可能发生异常,那么您需要自己捕获它并调用包含清理代码的辅助函数。
__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.
我很好奇是否可以通过修改类的new
实现或其他一些禁止在没有上下文的情况下实例化对象的Python魔法来解决这个问题。
确保清理的示例:如
的文档中所述,如果__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.
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 上的contextlib2
。 <3.3
from contextlib import contextmanager
@contextmanager
def test_cm():
try:
# dangerous code
yield
except Exception, err
pass # do something
我就是这样做的。它以错误作为参数调用 __exit__()。如果 args[0] 包含错误,它将在执行清理代码后重新引发异常。
按照设计,采用 Python 编程语言,
当
__enter__
函数抛出错误时,意味着未获取到要获取的资源,因此没有理由调用。 问题中没有解释在哪种情况下你想调用__exit__
__exit__
,但最常见的情况是
创建一个处理两个资源的上下文管理器--- 换句话说,组合多个上下文生成器。 对此,我认为最简单的解决方案是使用
@contextmanager
和一个函数 --- 事实上,我什至不知道如何正确编写一个类。
@contextmanager
def nest_resource(a, b):
with a as aa, b as bb:
yield (aa, bb)
如果以上情况不适合您,
您可以使用文档中的配方 __enter__
实现
中进行清理。重要的部分是:
class ResourceManager:
@contextmanager
def _cleanup_on_error(self):
with ExitStack() as stack:
stack.push(self)
yield
stack.pop_all()
def __enter__(self):
resource = self.acquire_resource()
with self._cleanup_on_error():
if not self.check_resource_ok(resource):
raise RuntimeError(msg.format(resource))
return resource
def __exit__(self, *exc_details):
self.release_resource()
它的工作原理是:
如果
acquire_resource
__exit__
将不会被调用。 如果
check_resource_ok
False
),则将调用 __exit__
。如果__enter__
__exit__
就不会被召唤。
__exit__
在
__enter__
失败时始终被调用,只需在with
块之前不放置任何内容即可。