[Python-Dev] PEP 572: Assignment Expressions (original) (raw)

Chris Angelico rosuav at gmail.com
Sat Apr 21 22:57:36 EDT 2018


On Sun, Apr 22, 2018 at 12:04 PM, Mike Miller <python-dev at mgmiller.net> wrote:

Round 2 (Changed order, see below):

1. with open(fn) as f: # current behavior 2. with (open(fn) as f): # same 3. with closing(urlopen(url)) as dl: # current behavior 5. with (closing(urlopen(url)) as dl): # same 4. with closing(urlopen(url) as dl): # urlopener named early

On 2018-04-20 17:15, Chris Angelico wrote:

The second and fifth could be special cased as either the same as first and third, or as SyntaxErrors. (But which?) If they are expressions, they should be the same once evaluated, no? (I had a brief episode where I wrote that "as" was required with "with", instead of the CM object, sorry. :) The fourth one is very tricky. If 'expr as name' is allowed inside arbitrary expressions, why shouldn't it be allowed there? Yes, they should be allowed there. The disconnect between viable syntax and useful statements is problematic here. Number 4 appears to name the urlopener early. Since closing() returns it as well, might it work anyway? Might be missing something else, but #4 looks like a mistake with the layout of the parentheses, which can happen anywhere. I don't get the sense it will happen often.

It's actually semantically identical to option 3, but not semantically identical to option 5, unless there is a magical special case that says that a 'with' statement is permitted to have parentheses for no reason. The 'closing' context manager returns the inner CM, not the closing CM itself. If we rewrite these into approximate equivalents without the 'with' statement, what we have is this:

1. with open(fn) as f: # current behavior

file = open(fn) f = file.enter() assert file is f # passes for file objects

2. with (open(fn) as f): # same

f = open(fn) f.enter()

The return value from enter is discarded

3. with closing(urlopen(url)) as dl: # current behavior

downloader = urlopen(url) closer = closing(downloader) dl = closer.enter() assert dl is downloader # passes for closing objects

5. with (closing(urlopen(url)) as dl): # same

downloader = urlopen(url) dl = closing(downloader) dl.enter()

Return value from enter is discarded

4. with closing(urlopen(url) as dl): # urlopener named early

dl = urlopen(url) closer = closing(dl) closer.enter()

Return value is discarded again

Notice how there are five distinctly different cases here. When people say there's a single obvious way to solve the "with (expr as name):" case, they generally haven't thought about all the different possibilities. (And I haven't mentioned the possibility that enter returns something that you can't easily reference from inside the expression, though it's not materially different from closing().)

There are a few ways to handle it. One is to create a special case in the grammar for 'with' statement parentheses:

with_stmt: 'with' with_item (',' with_item)* ':' suite with_item: (test ['as' expr]) | ('(' test ['as' expr] ')')

which will mean that these two do the same thing:

with spam as ham:
with (spam as ham):

but this won't:

with ((spam as ham)):

And even with that special case, the use of 'as' inside a 'with' statement is subtly different from its behaviour anywhere else, so it would be confusing. So a better way is to straight-up disallow 'as' expressions inside 'with' headers (meaning you get a SyntaxError if the behaviour would be different from the unparenthesized form). Still confusing ("why can't I do this?"). And another way is to just not use 'as' at all, and pick a different syntax. That's why the PEP now recommends ':='.

ChrisA



More information about the Python-Dev mailing list