即使__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
在后视图中,上下文管理器可能不是最佳设计决策
答案 0 :(得分:19)
像这样:
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
。
答案 1 :(得分:8)
没有。如果__enter__()
中有可能发生异常,那么您需要自己捕获并调用包含清理代码的辅助函数。
答案 2 :(得分:2)
您可以使用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
来自the docs的示例:
stack = ExitStack()
try:
x = stack.enter_context(cm)
except Exception:
# handle __enter__ exception
else:
with stack:
# Handle normal case
答案 3 :(得分:1)
如果不需要继承或复杂子程序,可以使用更短的方法:
from contextlib import contextmanager
@contextmanager
def test_cm():
try:
# dangerous code
yield
except Exception, err
pass # do something
答案 4 :(得分:1)
我建议您遵循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
答案 5 :(得分:0)
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]包含错误,则在执行清理代码后重新启动异常。
答案 6 :(得分:0)
The docs包含使用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.