Chain dynamic iterable of context managers to a single with statement
Asked Answered
T

2

8

I have a bunch of context managers that I want to chain. On the first glance, contextlib.nested looked like a fitting solution. However, this method is flagged as deprecated in the documentation which also states that the latest with statement allows this directly:

Deprecated since version 2.7: The with-statement now supports this functionality directly (without the confusing error prone quirks).

However I could not get Python 3.4.3 to use a dynamic iterable of context managers:

class Foo():
    def __enter__(self):
        print('entering:', self.name)
        return self
    def __exit__(self, *_):
        pass
    def __init__(self, name):
        self.name = name

foo = Foo('foo')
bar = Foo('bar')

whether chaining:

from itertools import chain
m = chain([foo], [bar])
with m:
     pass

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: __exit__
m = [foo, bar]

providing the list directly:

with m:
     pass

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: __exit__

or unpacking:

with (*m):
    pass

  File "<stdin>", line 1
SyntaxError: can use starred expression only as assignment target

So, how do I properly chain a dynamic amount of context managers in a with statement correctly?

Technetium answered 22/6, 2015 at 13:50 Comment(2)
with doesn't take an iterable, no. with takes multiple contextmanagers, but not as a list.Sulphanilamide
@MartijnPieters That's bad news. So I would need to write a chaining class on my own?Technetium
S
17

You misunderstood that line. The with statement takes more than one context manager, separated by commas, but not an iterable:

with foo, bar:

works.

Use a contextlib.ExitStack() object if you need to support a dynamic set of context managers:

from contextlib import ExitStack

with ExitStack() as stack:
    for cm in (foo, bar):
        stack.enter_context(cm)
Sulphanilamide answered 22/6, 2015 at 13:58 Comment(0)
S
0

The "multiple manager form of the with statement", as shown in the statement's documentation, would be:

with foo, bar:

i.e. it doesn't support a dynamic number of managers. As the documentation for contextlib.nested notes:

Developers that need to support nesting of a variable number of context managers can either use the warnings module to suppress the DeprecationWarning raised by this function or else use this function as a model for an application specific implementation.

Simony answered 22/6, 2015 at 13:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.