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()