Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create a "with" block on several context managers? [duplicate]

In Python 2.7 and 3.1 and above, you can write:

with A() as X, B() as Y, C() as Z:
    do_something()

This is normally the best method to use, but if you have an unknown-length list of context managers you'll need one of the below methods.


In Python 3.3, you can enter an unknown-length list of context managers by using contextlib.ExitStack:

with ExitStack() as stack:
    for mgr in ctx_managers:
        stack.enter_context(mgr)
    # ...

This allows you to create the context managers as you are adding them to the ExitStack, which prevents the possible problem with contextlib.nested (mentioned below).

contextlib2 provides a backport of ExitStack for Python 2.6 and 2.7.


In Python 2.6 and below, you can use contextlib.nested:

from contextlib import nested

with nested(A(), B(), C()) as (X, Y, Z):
    do_something()

is equivalent to:

m1, m2, m3 = A(), B(), C()
with m1 as X:
    with m2 as Y:
        with m3 as Z:
            do_something()

Note that this isn't exactly the same as normally using nested with, because A(), B(), and C() will all be called initially, before entering the context managers. This will not work correctly if one of these functions raises an exception.

contextlib.nested is deprecated in newer Python versions in favor of the above methods.


UPDATE: Starting in python 3.10, you'll be able to use parenthesized context managers! Thanks @iforapsy!

with (
    mock.patch('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') as a,
    mock.patch('bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb') as b,
    mock.patch('cccccccccccccccccccccccccccccccccccccccccc') as c,
):
    do_something()

Original Answer For python versions < 3.10

@interjay's Answer is correct. However, if you need to do this for long context managers, for example mock.patch context managers, then you quickly realize you want to break this across lines. Turns out you can't wrap them in parens, so you have to use backslashes. Here's what that looks like:

with mock.patch('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa') as a, \
        mock.patch('bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb') as b, \
        mock.patch('cccccccccccccccccccccccccccccccccccccccccc') as c:
    do_something()

The first part of your question is possible in Python 3.1.

With more than one item, the context managers are processed as if multiple with statements were nested:

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

is equivalent to

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

Changed in version 3.1: Support for multiple context expressions


The second part of your question is solved with contextlib.ExitStack in Python 3.3.