Now that PEP 572 has been accepted, Python 3.8 is destined to have assignment expressions, so we can use an assignment expression in with
, i.e.
with (f := open('file.txt')):
for l in f:
print(f)
instead of
with open('file.txt') as f:
for l in f:
print(f)
and it would work as before.
What use does the as
keyword have with the with
statement in Python 3.8? Isn't this against the Zen of Python: "There should be one -- and preferably only one -- obvious way to do it."?
When the feature was originally proposed, it wasn't clearly specified whether the assignment expression should be parenthesized in with
and that
with f := open('file.txt'):
for l in f:
print(f)
could work. However, in Python 3.8a0,
with f := open('file.txt'):
for l in f:
print(f)
will cause
File "<stdin>", line 1
with f := open('file.txt'):
^
SyntaxError: invalid syntax
but the parenthesized expression works.
TL;DR: The behaviour is not the same for both constructs, even though there wouldn't be discernible differences between the 2 examples.
You should almost never need
:=
in awith
statement, and sometimes it is very wrong. When in doubt, always usewith ... as ...
when you need the managed object within thewith
block.In
with context_manager as managed
,managed
is bound to the return value ofcontext_manager.__enter__()
, whereas inwith (managed := context_manager)
,managed
is bound to thecontext_manager
itself and the return value of the__enter__()
method call is discarded. The behaviour is almost identical for open files, because their__enter__
method returnsself
.The first excerpt is roughly analogous to
whereas the
as
form would bei.e.
with (f := open(...))
would setf
to the return value ofopen
, whereaswith open(...) as f
bindsf
to the return value of the implicit__enter__()
method call.Now, in case of files and streams,
file.__enter__()
will returnself
if it succeeds, so the behaviour for these two approaches is almost the same - the only difference is in the event that__enter__
throws an exception.The fact that assignment expressions will often work instead of
as
is deceptive, because there are many classes where_mgr.__enter__()
returns an object that is distinct fromself
. In that case an assignment expression works differently: the context manager is assigned, instead of the managed object. For exampleunittest.mock.patch
is a context manager that will return the mock object. The documentation for it has the following example:Now, if it were to be written to use an assignment expression, the behaviour would be different:
mock_thing
is now bound to the context manager instead of the new mock object.