Создать блок с несколькими менеджерами контекста?

Предположим, у вас есть три объекта, которые вы получаете через диспетчер контекста, например, блокировку, соединение с базой данных и 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

Если ответ «нет», то это потому, что необходимость в такой функции подразумевает плохой дизайн, или, может быть, я должен предложить ее в качестве напоминания? :-П


person olamundo    schedule 11.06.2010    source источник
comment
@timgeb Я начал обсуждение этого дублирующего закрытия. Приношу свои извинения за то, что @ не уведомил вас раньше; это выскользнуло из моей головы.   -  person Graham    schedule 06.01.2019


Ответы (5)


В Python 2.7 и 3.1 и более поздних версиях вы можете написать:

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 (упомянутые ниже).

contextlib2 предоставляет резервный порт ExitStack для Python 2.6 и 2.7.


В 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() будут вызываться изначально до входа в диспетчеры контекста. Это не будет работать правильно, если одна из этих функций вызовет исключение.

contextlib.nested устарел в новых версиях Python в пользу вышеуказанных методов.

person interjay    schedule 11.06.2010
comment
Благодарность! Так что я могу использовать это для массива диспетчеров контекста с contextlib.nested(*arr). ‹Br› Возможно ли это каким-то образом в новом синтаксисе Python 2.7 и 3.1? - person olamundo; 12.06.2010
comment
@noam: Нет, на самом деле в строке документации для nested в 3.1 сказано: Одно преимущество этой функции перед формой с несколькими диспетчерами оператора with заключается в том, что распаковка аргументов позволяет использовать ее с переменным числом диспетчеров контекста следующим образом: with nested(*managers): do_something() - person interjay; 12.06.2010
comment
Странно, с одной стороны, он устарел, но с другой стороны, они признают преимущество устаревшего модуля над заменой? - person olamundo; 12.06.2010
comment
@noam: нет, contextlib.ExitStack заменяет contextlib.nested(). - person Martijn Pieters; 27.05.2013
comment
Но обычно это не имеет значения. Если B() или C() вызывает исключение, огромная разница. Вложенный оператор with вызовет A().__exit__(), но установка contextlib.nested() - нет! - person Martijn Pieters; 27.05.2013
comment
@MartijnPieters: Да, "usually" было определенно слишком сильным словом. Я прояснил это. И спасибо за ссылку на новый ExitStack, пример я добавил. - person interjay; 27.05.2013
comment
+1: за добавление ExitStack и завершение ответа - person Neil G; 19.06.2013
comment
Одна проблема: использование синтаксиса simple с open (A) как a, open (B) как b: style, разрывы строк делают соответствие pep8, как сообщается стандартными инструментами, невозможным. Я использую обратную косую черту для обозначения разрыва строки, потому что окружение выражений, разделенных запятыми, круглыми скобками приводит к сообщению о синтаксической ошибке. С обратной косой чертой я получаю предупреждение о чрезмерном отступе строки продолжения E127. Мне еще предстоит найти способ использовать этот синтаксис при подавлении всех предупреждений. - person Darren Ringer; 09.03.2017
comment
И есть идеи по поводу полной замены contextlib.nested в самом Python 2.7? - person igordc; 29.05.2017
comment
@igordcard contextlib.nested все еще находится в Python 2.7, но помечен как устаревший. В документах говорится, что разработчики, которым необходимо поддерживать вложение переменного числа диспетчеров контекста, могут либо использовать модуль предупреждений для подавления DeprecationWarning, вызываемого этой функцией, либо использовать эту функцию в качестве модели для конкретной реализации приложения. - person interjay; 29.05.2017
comment
@DarrenRinger У меня была такая же проблема. Для этого я тоже использовал обратную косую черту, которую ненавижу. Сделайте двойной отступ для всех менеджеров контекста после начальной строки with. Только один отступ для оборачиваемого содержимого. Для меня это проходит flake8. - person sage88; 29.09.2017
comment
@MartijnPieters Важно отметить, что вызов nested не будет вызывать __enter__ методы любого из менеджеров контекста, если B() или C() вызовут исключение. Это могло быть желательной разницей. Это хорошая причина не допускать появления побочных эффектов в инициализаторах и помещать их в __enter__, что вам и следует делать в любом случае. Я бы сказал, что нарушение этого соглашения не является питоническим. Пока контекстные менеджеры следуют этому соглашению, никакого риска нет. (Интерджай: я бы порекомендовал включить эту деталь в ответ, или я мог бы сделать это, если вы не возражаете.) - person jpmc26; 03.10.2017
comment
@ jpmc26 К сожалению, это не поможет с файловыми объектами, которые, вероятно, наиболее часто используются для оператора with. То же самое будет верно и для других подобных объектов, для которых использование оператора with необязательно. Кроме того, есть еще одна проблема с nested, упомянутая в документации: если метод __enter __ () одного из менеджеров внутреннего контекста вызывает исключение, которое перехватывается и подавляется методом __exit __ () одного из менеджеров внешнего контекста, эта конструкция вызовет RuntimeError вместо того, чтобы пропускать тело оператора with. - person interjay; 04.10.2017
comment
Этот ответ идеален. ExitStack - это именно то, что я искал. Большое спасибо за то, что держите это в курсе. - person Boris; 07.04.2020

Первая часть вашего вопроса возможна в Python 3.1.

С более чем одним элементом диспетчеры контекста обрабатываются так, как если бы несколько операторов with были вложены:

with A() as a, B() as b:
    suite

эквивалентно

with A() as a:
    with B() as b:
        suite

Изменено в версии 3.1: поддержка нескольких контекстных выражений

person Mark Byers    schedule 11.06.2010
comment
Благодарность! но это все еще не ответило на мой вопрос: как насчет второго случая, о котором я упоминал, когда менеджеры контекста даны в массиве, не зная, сколько менеджеров в массиве. возможно ли в каком-нибудь python3.X сделать with [cm1,cm2,cm3,cm4,cm5] as result: .... - person olamundo; 11.06.2010
comment
@noam: Чтобы решить вторую часть вашего вопроса, вы можете написать класс, чтобы обернуть ряд ресурсов и реализовать __enter__ и __exit__ для этого класса. Я не уверен, есть ли класс стандартной библиотеки, который это уже делает. - person Mark Byers; 11.06.2010
comment
@Mark Я не думаю, что это так просто - поэтому contextlib.nested() устарел. Если что-то произойдет между генерацией других вещей и активацией диспетчера контекста, может случиться так, что очистка не произойдет так, как хотелось бы. - person glglgl; 27.05.2013

Ответ @ interjay правильный. Однако, если вам нужно сделать это для менеджеров длинного контекста, например менеджеров контекста mock.patch, вы быстро поймете, что хотите разбить это на несколько строк. Оказывается, вы не можете заключить их в скобки, поэтому вам нужно использовать обратную косую черту. Вот как это выглядит:

with mock.patch('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') as a, \
        mock.patch('bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb') as b, \
        mock.patch('cccccccccccccccccccccccccccccccccccccccccc') as c:
    do_something()
person sage88    schedule 29.09.2017
comment
уродливый. Почему бы не разрешить упаковку в парные скобки (меня это укусило) - person John Deighan; 23.10.2019
comment
@JohnDeighan Я согласен, это некрасиво. Меня это тоже укусило, поэтому я опубликовал это. Я надеюсь, что это было полезно, хотя я согласен, я бы хотел, чтобы он поддерживал парен-упаковку. - person sage88; 30.10.2019
comment
@JohnDeighan Почему разрывы строк с \ ugrо менее жесткими, чем с использованием скобок? - person isarandi; 11.11.2019
comment
Чтобы избежать обратной косой черты, вы можете отформатировать его вот так (нужно было связать его как изображение , так как комментарии не допускают разрывов строки, и этот вопрос закрыт для дополнительных ответов). - person Stef; 30.03.2020
comment
mock.patch должен оценивать объект. можем ли мы также написать a_ctx = mock.patch('a'); ...; with a_ctx as a, ...: - person ThorSummoner; 23.12.2020

Вторая часть вашего вопроса решена с помощью contextlib.ExitStack в Python 3.3.

person Neil G    schedule 04.05.2013

Следуя ответу @ sage88, вы всегда можете присвоить этим патчам осмысленные имена переменных, прежде чем вводить их.

Вы можете создать эти патчи в несколько строк

a_patch = mock.patch('aaaaaaa') 
b_patch = mock.patch('bbbbbbb')
c_patch = mock.patch('ccccccc') 
with a_patch as a, b_patch as b, c_patch as c:    
    do_something()
person poulter7    schedule 19.10.2018