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 awithstatement, and sometimes it is very wrong. When in doubt, always usewith ... as ...when you need the managed object within thewithblock.In
with context_manager as managed,managedis bound to the return value ofcontext_manager.__enter__(), whereas inwith (managed := context_manager),managedis bound to thecontext_manageritself 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
asform would bei.e.
with (f := open(...))would setfto the return value ofopen, whereaswith open(...) as fbindsfto the return value of the implicit__enter__()method call.Now, in case of files and streams,
file.__enter__()will returnselfif 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
asis 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.patchis 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_thingis now bound to the context manager instead of the new mock object.