[Python-ideas] Spelling of Assignment Expressions PEP 572 (was post #4)

Steven D'Aprano steve at pearwood.info
Fri Apr 13 09:18:59 EDT 2018


On Fri, Apr 13, 2018 at 09:56:35PM +1000, Chris Angelico wrote:


> How many times have people asked for "with (expr as name):" to
> be supported, allowing the statement to spread over multiple lines?
> With this syntax, it would suddenly be permitted - with dangerously
> similar semantics.

I see your point, but why don't we support "with (expr as name):" to 
allow multiple lines? No, don't answer that... its off-topic. Forget I 
asked.

In any case, we already allow similar syntax with different meaning in 
different places, for example, something that looks just like assignment 
inside expressions:

    functions = [len, ord, map, lambda x, y=1: x+y]

But its not really an assignment as such, its a parameter declaration.

If we agree that the benefit of putting the expression first is 
sufficiently large, or that the general Pythonic look of "expr as name" 
is sufficiently desirable (it just looks and reads nicely), then we can 
afford certain compromises. Namely, we can rule that:

    except expr as name:
    with expr as name:

continue to have the same meaning that they have now and never mean 
assignment expressions. Adding parens should not change that.

If you try to write something like:

    except (spam or eggs as cheese) or function(cheese) as name: 
    with (spam or eggs as cheese) or function(cheese) as name: 

etc (or any other assignment expression, effectively anything which 
isn't currently allowed) then you get a syntax error.

So this:

    with expr as name:
        process(name)

will only work if expr returns an object with a context manager. But 
that's they way it works now, so nothing really changes.

In other words, the rule is that "expr as name" keeps its current, older 
semantics in with and except statements, and NEVER means the new, PEP 
572 assignment expression.

Yes, that's a special case that breaks the rules, and I accept that it 
is a point against "as". But the Zen is a guideline, not a law of 
physics, and I think the benefits of "as" are sufficient that even 
losing a point it still wins.


> For many MANY context managers, "with (expr as
> name):" would do the exact same thing as "with expr as name:". There
> is a general expectation that adding parentheses to an expression
> usually doesn't change the behaviour, and if it's legal, people will
> assume that the behaviour is the same. It isn't, and it's such a
> sneaky difference that I would call it a bug magnet.

Indeed. I wouldn't allow such a subtle difference in behaviour due to 
parens. That reminds me of the Python 1 and early 2.x except clauses, 
where

    except ValueError, TypeError:

    except (ValueError, TypeError):

meant different things. I still shudder at that one.


> So if it's a bug magnet, what do we do?
> 
> 1) Permit the subtly different semantics, and tell people to be careful

No.


> 2) Forbid any use of "(expr as name)" in the header of a 'with' statement

You can't forbid it, because it is currently allowed syntax (albeit 
currently without the parens). So the rule is, it is allowed, but it 
means what it meant pre-PEP 572.


> 3) Forbid it at top level, but permit it deeper down

I don't know what that means. But whatever it means, probably no :-)


> 4) Something else??

Well, there's always the hypothetical -> arrow binding operator, or the 
Pascal := assignment operator (the current preference).

I don't hate the := choice, I just think it is more Pascal-esque that 
Pythonic :-)


> > I don't especially dislike := but I really think that putting the
> > expression first is a BIG win for readability. If that requires parens
> > to disambiguate it, so be it.
> 
> There's a mild parallel between "(expr as name)" and other uses of
> 'as', which bind to that name. Every other use of 'as' is part of a
> special syntactic form ('import', 'with', and 'except'), but they do
> all bind to that name. (Point of interest: You can "with expr as
> x[0]:" but none of the other forms allow anything other than a simple
> name.)

I disagree: I think it is a strong parallel. They're both name bindings. 
How much stronger do you want?

True, we don't currently allow such things as

    import math as maths, mathematics, spam.modules[0]

but we could if we wanted to and there was a sensible use-case for it.


> There's a strong parallel between "target := value" and "target
> = value"; 

Sure. And for a statement, either form would be fine. I just think that 
in an expression, it is important enough to bring the expression to the 
front, even if it requires compromise elsewhere.


[...]
> I actually can't find anything about the -> operator, only the <- one.
> (Not that I looked very hard.) Is it a truly viable competitor, or
> just one that you'd like to see mentioned for completeness?

Yes, as I mentioned in another post, R allows both -> and <-, some 
language called BETA uses ->, various calculator BASICs use -> (albeit 
with a special single character, not a digraph) as does HP RPN.

Here's an example from R:

> c(1, 2, 3+4, 5) -> data
> data
[1] 1 2 7 5

But whether it is viable or not depends on *us*, not what other 
languages do. No other language choose the syntax of ternary if 
expression before Python used it. We aren't limited to only using syntax 
some other language used first.


> > I think that there should be more attention paid to the idea of putting
> > the expression first, rather than the name.
> 
> How many ways are there to bind a value to a name?
[...]
> I'm seeing consistency here in that *EVERY* name binding where the
> name is at the end uses "as target" as its syntax. Everything else
> starts with the target, then defines what's being assigned to it. So I
> don't see much value in a "->" operator, except for the mere fact that
> it's different (and thus won't conflict in except/with); and the bulk
> of name bindings in Python put the name first.

We shouldn't be choosing syntax because other syntax does the same. We 
should pick the syntax which is most readable and avoids the most 
problems. That's why Guido bucked the trends of half a century of 
programming languages, dozens of modern languages, and everything else 
in Python, to put the conditional in the middle of ternary if instead of 
the beginning or end. (And he was right to do so -- I like Python's 
ternary operator, even if other people think it is weird.)

If people agree with me that it is important to put the expression first 
rather than the target name, then the fact that statements and for loops 
put the name first shouldn't matter.

And if they don't, then I'm outvoted :-)



-- 
Steve


More information about the Python-ideas mailing list