Краткий ответ : возможно возможно .Но контекстные менеджеры позволяют реализовать некоторую логику, что делает «сложным» реализовать ее абсолютно правильным образом.Ниже вы видите «Подтверждение концепции», но я не гарантирую, что он имеет точно такое же поведение.Поэтому я действительно советую работать с вложенными with
s.
То, что здесь не рассматривается : __init__
или __enter__
также могут вызывать исключения, и тогда ониобрабатываются «внешними» контекстными менеджерами.Это делает это, конечно, довольно сложным.По сути, вам нужно «собрать» стек в __enter__
, а затем «вытолкнуть» стек в случае сбоя одного из __enter__
.Этот сценарий не описан здесь.
Мы можем создать «составной» контекстный менеджер:
class C:
def __init__(self, *ctxs):
self.ctxs = ctxs
def __enter__(self):
return tuple(ctx.__enter__() for ctx in self.ctxs)
def __exit__(self, self, exception_type, exception_value, traceback):
for ctx in reversed(self.ctxs):
try:
if ctx.__exit__(exception_type, exception_value, traceback):
(exception_type, exception_value, traceback) = (None,) * 3
except Exception as e:
exception_value = e
traceback = e.__traceback__
exception_type = type(e)
return exception_value is None
Часть __exit__
сложна.Прежде всего, нам нужно выйти в обратном порядке.Но обработка исключений еще более сложна: если __exit__
заставляет исключение замолчать, возвращая «правдивое» значение, то мы должны передать (None, None, None)
как (exception_type, exeption_value, traceback)
, но проблема, которая может возникнуть, заключается в том, что __exit__
с другой стороны, запускает само исключение и, следовательно, вводит новое исключение.
Затем мы можем использовать процессор контекста, например:
with C(A(), B()) as (a,b):
# ...
pass
Таким образом, приведенное выше позволяет реализовать менеджер контекста.для произвольного числа «субконтекст-менеджеров».Мы можем создать подкласс этого класса, например:
class ContextAB(C):
def __init__(self):
super(ContextAB, self).__init__(A(), B())
, а затем использовать его как:
with ContextAB() as (a, b):
# ...
pass
Но вкратце: используйте вложенные операторы with
.Это также делает более ясным, что здесь происходит.Прямо сейчас C
инкапсулирует все виды логики, которые лучше сделать явными.Если ввод B
завершится неудачно, то это должно привести к исключению, которое обрабатывается __exit__
из A
и т. Д. Это делает очень трудным получение "деталей", полностью эквивалентныхсемантика оператора with
.