Multiple variables in a 'with' statement?
Is it possible to declare more than one variable using a with
statement in Python?
Something like:
from __future__ import with_statement
with open("out.txt","wt"), open("in.txt") as file_out, file_in:
for line in file_in:
file_out.write(line)
... or is cleaning up two resources at the same time the problem?
Solution 1:
It is possible in Python 3 since v3.1 and Python 2.7. The new with
syntax supports multiple context managers:
with A() as a, B() as b, C() as c:
doSomething(a,b,c)
Unlike the contextlib.nested
, this guarantees that a
and b
will have their __exit__()
's called even if C()
or it's __enter__()
method raises an exception.
You can also use earlier variables in later definitions (h/t Ahmad below):
with A() as a, B(a) as b, C(a, b) as c:
doSomething(a, c)
As of Python 3.10, you can use parentheses:
with (
A() as a,
B(a) as b,
C(a, b) as c,
):
doSomething(a, c)
Solution 2:
Note that if you split the variables into lines, you must use backslashes to wrap the newlines.
with A() as a, \
B() as b, \
C() as c:
doSomething(a,b,c)
Parentheses don't work, since Python creates a tuple instead.
with (A(),
B(),
C()):
doSomething(a,b,c)
Since tuples lack a __enter__
attribute, you get an error (undescriptive and does not identify class type):
AttributeError: __enter__
If you try to use as
within parentheses, Python catches the mistake at parse time:
with (A() as a,
B() as b,
C() as c):
doSomething(a,b,c)
SyntaxError: invalid syntax
When will this be fixed?
This issue is tracked in https://bugs.python.org/issue12782.
Recently, Python announced in PEP 617 that they'll be replacing the current parser with a new one. Because Python's current parser is LL(1), it cannot distinguish between "multiple context managers" with (A(), B()):
and "tuple of values" with (A(), B())[0]:
.
The new parser can properly parse multiple context managers surrounded by parentheses. The new parser has been enabled in 3.9, but this syntax will still be rejected until the old parser is removed in Python 3.10.
Solution 3:
contextlib.nested
supports this:
import contextlib
with contextlib.nested(open("out.txt","wt"), open("in.txt")) as (file_out, file_in):
...
Update:
To quote the documentation, regarding contextlib.nested
:
Deprecated since version 2.7: The with-statement now supports this functionality directly (without the confusing error prone quirks).
See Rafał Dowgird's answer for more information.