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

Steven D'Aprano steve at pearwood.info
Sun Apr 15 11:58:06 EDT 2018


On Sun, Apr 15, 2018 at 10:21:02PM +1000, Chris Angelico wrote:

> I don't think we're ever going to unify everyone on an arbitrary
> question of "expression first" or "name first". But to all the
> "expression first" people, a question: what if the target is not just
> a simple name?
> 
> while (read_next_item() -> items[i + 1 -> i]) is not None:
>     print("%d/%d..." % (i, len(items)), end="\r")

I don't see why it would make a difference. It doesn't to me.


> Does this make sense? With the target coming first, it perfectly
> parallels the existing form of assignment:

Yes, except this isn't ordinary assignment-as-a-statement.

I've been mulling over the question why I think the expression needs to 
come first here, whereas I'm satisfied with the target coming first for 
assignment statements, and I think I've finally got the words to explain 
it. It is not just long familiarity with maths and languages that put 
the variable first (although that's also part of it). It has to do with 
what we're looking for when we read code, specifically what is the 
primary piece of information we're initially looking for.

In assignment STATEMENTS the primary piece of information is the target. 
Yes, of course the value assigned to the target is important, but often 
we don't care what the value is, at least not at first. We're hunting 
for a known target, and only when we find it do we care about the value 
it gets.

A typical scenario: I'm reading a function, and I scan down the block 
looking at the start of each line until I find the variable I want:

    spam = don't care
    eggs = don't care
    self.method(don't care)
    cheese = ... <<<==== HERE IT IS

so it actually helps to have the name up front. Copying standard maths 
notation for assignment (variable first, value second) is a good thing 
for statements.

With assignment-statements, if you're scanning the code for a variable 
name, you're necessarily interested in the name and it will be helpful 
to have it on the left.

But with assignment-expressions, there's an additional circumstance: 
sometimes you don't care about the name, you only care what the value 
is. (I expect this will be more common.) The name is just something 
to skip over when you're scanning the code looking for the value.

    # what did I pass as the fifth argument to the function?
    result = some_func(don't care, spam := don't care, eggs := don't care,
                       self.method(don't care), cheese := HERE IT IS, 
                       ...)

Of course it's hard counting commas so it's probably better to add a bit 
of structure to your function call:

    result = some_func(don't care, 
                       spam := don't care, 
                       eggs := don't care,
                       self.method(don't care), 
                       cheese := HERE IT IS, 
                       ...)


But this time we don't care about the name. Its the value we care about:

    result = some_func(don't care, 
                       don't care -> don't care
                       don't care -> don't care
                       don't care(don't care), 
                       HERE IT IS .... ,
                       ...)


The target is just one more thing you have to ignore, and it is helpful 
to have expression first and the target second.

Some more examples:

    # what am I adding to the total?
    total += don't care := expression

    # what key am I looking up?
    print(mapping[don't care := key])

    # how many items did I just skip?
    self.skip(don't care := obj.start + extra)


versus

    total += expression -> don't care
    print(mapping[key -> don't care])
    self.skip(obj.start + extra -> don't care)


It is appropriate for assignment statements and expressions to be 
written differently because they are used differently.



[...]
> >>> items = [None] * 10
> >>> i = -1
> >>> items[i := i + 1] = input("> ")
> > asdf
> >>> items[i := i + 1] = input("> ")
> > qwer
> >>> items[i := i + 1] = input("> ")
> > zxcv
> >>>
> >>> items
> ['asdf', 'qwer', 'zxcv', None, None, None, None, None, None, None]


I don't know why you would write that instead of:

items = [None]*10
for i in range(3):
    items[i] = input("> ")


or even for that matter:

items = [input("> ") for i in range(3)] + [None]*7


but whatever floats your boat. (Python isn't just not Java. It's also 
not C *wink*)


> Are you as happy with that sort of complex
> expression coming after 'as' or '->'?

Sure. Ignoring the output of the calls to input():

items = [None] * 10
i = -1
items[i + 1 -> i] = input("> ")
items[i + 1 -> i] = input("> ")
items[i + 1 -> i] = input("> ")


which isn't really such a complex target. How about this instead?


obj = SimpleNamespace(spam=None, eggs=None, 
                      aardvark={'key': [None, None, -1]}
                      )
items[obj.aardvark['key'][2] + 1 -> obj.aardvark['key'][2]] = input("> ")

versus:

items[obj.aardvark['key'][2] := obj.aardvark['key'][2] + 1] = input("> ")


Neither is exactly a shining exemplar of great code designed for 
readability. But putting the target on the right doesn't make it worse.



-- 
Steve


More information about the Python-ideas mailing list