Python как безопасно обрабатывать исключение внутри менеджера контекста
Я думаю, что я прочитал, что исключения внутри with
не позволяют правильному вызову __exit__
. Если я ошибаюсь в этой заметке, прошу простить мое невежество.
Итак, у меня есть некоторый псевдокод, моя цель - использовать контекст блокировки, который при __enter__
записывает начало datetime и возвращает идентификатор блокировки, а после __exit__
записывает конец datetime и освобождает блокировку:
def main():
raise Exception
with cron.lock() as lockid:
print('Got lock: %i' % lockid)
main()
Как я могу все еще повышать ошибки в дополнение к существующему контексту?
Примечание. Я намеренно поднимаю базовое исключение в этом псевдокоде, поскольку я хочу выйти безопасно при любом исключении, а не только ожидаемые исключения.
Примечание. Альтернативные/стандартные методы предотвращения concurrency не имеют значения, я хочу применить эти знания к любому управлению общим контекстом. Я не знаю, имеют ли разные контексты разные причуды.
PS. Соответствует ли блок finally
?
Ответы
Ответ 1
Метод __exit__
называется нормальным, если контекстный менеджер разбит на исключение. Фактически, параметры, переданные в __exit__
, имеют отношение к обработке этого случая! Из документы:
object.__exit__(self, exc_type, exc_value, traceback)
Выход из контекста среды выполнения, связанного с этим объектом. Параметры описывают исключение, вызвавшее выход из контекста. Если контекст был исключен без исключения, все три аргумента будут None.
Если предоставляется исключение, и метод хочет подавить исключение (т.е. предотвратить его распространение), он должен вернуть истинное значение. В противном случае исключение будет обработано обычно после выхода из этого метода.
Обратите внимание, что методы __exit__()
не должны повторно обрабатывать исключение прошедшего; это ответственная сторона.
Итак, вы можете видеть, что метод __exit__
будет выполнен, а затем по умолчанию любое исключение будет повторно поднято после выхода из контекстного менеджера. Вы можете проверить это самостоятельно, создав простой менеджер контекстов и разбив его на исключение:
DummyContextManager(object):
def __enter__(self):
print('Entering...')
def __exit__(self, exc_type, exc_value, traceback):
print('Exiting...')
# If we returned True here, any exception would be suppressed!
with DummyContextManager() as foo:
raise Exception()
Когда вы запускаете этот код, вы должны видеть все, что хотите (может быть, не в порядке, поскольку print
имеет тенденцию попадать в середину трассировки):
Entering...
Exiting...
Traceback (most recent call last):
File "C:\foo.py", line 8, in <module>
raise Exception()
Exception
Ответ 2
Лучшая практика при использовании @contextlib.contextmanager
была не совсем понятна для меня из приведенного выше ответа. Я перешел по ссылке link в комментарии @BenUsman.
Если вы пишете менеджер контекста, вы должны заключить блок yield
в блок try-finally
:
from contextlib import contextmanager
@contextmanager
def managed_resource(*args, **kwds):
# Code to acquire resource, e.g.:
resource = acquire_resource(*args, **kwds)
try:
yield resource
finally:
# Code to release resource, e.g.:
release_resource(resource)
>>> with managed_resource(timeout=3600) as resource:
... # Resource is released at the end of this block,
... # even if code in the block raises an exception