Create a "with" block on several context managers?

olamundo picture olamundo · Jun 11, 2010 · Viewed 67.4k times · Source

Suppose you have three objects you acquire via context manager, for instance A lock, a db connection and an ip socket. You can acquire them by:

with lock:
   with db_con:
       with socket:
            #do stuff

But is there a way to do it in one block? something like

with lock,db_con,socket:
   #do stuff

Furthermore, is it possible, given an array of unknown length of objects that have context managers, is it possible to somehow do:

a=[lock1, lock2, lock3, db_con1, socket, db_con2]
with a as res:
    #now all objects in array are acquired

If the answer is "no", is it because the need for such a feature implies bad design, or maybe I should suggest it in a pep? :-P

Answer

interjay picture interjay · Jun 11, 2010

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.