[Python-Dev] PEP 572: Assignment Expressions
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