Исключение исключения в менеджере контекста __enter __() - программирование
Подтвердить что ты не робот

Исключение исключения в менеджере контекста __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

На заднем плане менеджер контекста, возможно, не был лучшим дизайнерским решением

4b9b3361

Ответ 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] содержит ошибку, он регрессирует исключение после выполнения кода очистки.