[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