Исключение исключения в менеджере контекста __enter __()
Можно ли гарантировать, что метод __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__
>>>
Edit
Это как можно ближе...
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
На заднем плане менеджер контекста, возможно, не был лучшим дизайнерским решением
Ответы
Ответ 1
Вот так:
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 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"
Насколько я могу судить, вы не можете полностью пропускать блокировку. И обратите внимание, что этот контекст теперь проглатывает все исключения. Если вы не хотите проглатывать исключения, если __enter__
преуспевает, проверьте self.enter_ok
в __exit__
и return False
, если он True
.
Ответ 2
Нет. Если есть вероятность того, что исключение может возникнуть в __enter__()
, вам нужно будет поймать его самостоятельно и вызвать вспомогательную функцию, содержащую код очистки.
Ответ 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
См. contextlib2 на Python < 3.3.
Ответ 4
Если наследование или сложные подпрограммы не требуются, вы можете использовать более короткий путь:
from contextlib import contextmanager
@contextmanager
def test_cm():
try:
# dangerous code
yield
except Exception, err
pass # do something
Ответ 5
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] содержит ошибку, он регрессирует исключение после выполнения кода очистки.