Python: создать блок "с" для нескольких менеджеров контекста
Предположим, у вас есть три объекта, которые вы приобретаете с помощью диспетчера контекстов, например, блокировка, соединение db и ip-сокет.
Вы можете приобрести их:
with lock:
with db_con:
with socket:
#do stuff
Но есть ли способ сделать это в одном блоке? что-то вроде
with lock,db_con,socket:
#do stuff
Кроме того, возможно ли, учитывая массив неизвестной длины объектов с контекстными менеджерами, можно как-то это сделать:
a=[lock1, lock2, lock3, db_con1, socket, db_con2]
with a as res:
#now all objects in array are acquired
Если ответ "нет", это потому, что потребность в такой функции подразумевает плохой дизайн, или, может быть, я должен предложить его в pep?:-P
Ответы
Ответ 1
В Python 2.6 и ниже вы можете использовать contextlib.nested
:
from contextlib import nested
with nested(A(), B(), C()) as (X, Y, Z):
do_something()
эквивалентно:
m1, m2, m3 = A(), B(), C()
with m1 as X:
with m2 as Y:
with m3 as Z:
do_something()
Обратите внимание, что это не совсем то же самое, что обычно с использованием вложенных with
, потому что сначала будут вызваны A()
, B()
и C()
, прежде чем вводить менеджеров контекста. Это не будет работать правильно, если одна из этих функций может вызвать исключения, но будет работать для примеров в вопросе.
В Python 2.7 и 3.1 для этого добавлен синтаксис, а contextlib.nested
устарел:
with A() as X, B() as Y, C() as Z:
do_something()
В Python 3.3 вы также можете ввести список менеджеров контекста с неизвестной длиной, используя contextlib.ExitStack
with ExitStack() as stack:
for mgr in ctx_managers:
stack.enter_context(mgr)
# ...
Это позволяет вам создавать контекстные менеджеры по мере добавления их к ExitStack
, что предотвращает возможную проблему с contextlib.nested
.
Ответ 2
Первая часть вашего вопроса возможна в Python 3.1.
С более чем одним элементом диспетчеры контекста обрабатываются так, как если бы несколько вложений были вложены:
with A() as a, B() as b:
suite
эквивалентно
with A() as a:
with B() as b:
suite
Изменено в версии 3.1: Поддержка нескольких выражений контекста
Ответ 3
Вторая часть вашего вопроса решена с помощью contextlib.ExitStack
в Python 3.3.
Ответ 4
@interjay Ответ правильный. Однако, если вам нужно сделать это для длинных менеджеров контекста, например менеджеров контекста mock.patch, то вы быстро поймете, что хотите разбить это по строкам. Оказывается, вы не можете обернуть их в parens, поэтому вам придется использовать обратную косую черту. Вот что это выглядит:
with mock.patch('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') as a, \
mock.patch('bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb') as b, \
mock.patch('cccccccccccccccccccccccccccccccccccccccccc') as c:
do_something()