Skip to content
Advertisement

Why aren’t augmented assignment expressions allowed?

I was recently reading over PEP 572 on assignment expressions and stumbled upon an interesting use case:

# Compute partial sums in a list comprehension
total = 0
partial_sums = [total := total + v for v in values]
print("Total:", total)

I began exploring the snippet on my own and soon discovered that :+= wasn’t valid Python syntax.

# Compute partial sums in a list comprehension
total = 0
partial_sums = [total :+= v for v in values]
print("Total:", total)

I suspect there may be some underlying reason in how := is implemented that wisely precludes :+=, but I’m not sure what it could be. If someone wiser in the ways of Python knows why :+= is unfeasible or impractical or otherwise unimplemented, please share your understanding.

Advertisement

Answer

The short version: The addition of the walrus operator was incredibly controversial, and they wanted to discourage overuse, so they limited it to only those cases for which a strong motivating use case was put forward, leaving = the convenient tool for all other cases.

There’s a lot of things the walrus operator won’t do that it could do (assigning to things looked up on a sequence or mapping, assigning to attributes, etc.), but it would encourage using it all the time, to the detriment of the readability of typical code. Sure, you could choose to write more readable code, ignoring the opportunity to use weird and terrible punctuation-filled nonsense, but if my (and many people’s) experience with Perl is any guide, people will use shortcuts to get it done faster now even if the resulting code is unreadable, even by them, a month later.

There are other minor hurdles that get in the way (supporting all of the augmented assignment approaches with the walrus would add a ton of new byte codes to the interpreter, expanding the eval loop’s switch significantly, and potentially inhibiting optimizations/spilling from CPU cache), but fundamentally, your motivating case of using a list comprehension for side-effects is a misuse of list comprehensions (a functional construct that, like all functional programming tools, is not intended to have side-effects), as are most cases that would rely on augmented assignment expressions. The strong motivations for introducing this feature were things you could reasonably want to do and couldn’t without the walrus, e.g.

  1. Regexes, replacing this terrible, verbose arrow pattern:

    m = re.match(r'pattern', string)
    if m:
        do_thing(m)
    else:
        m = re.match(r'anotherpattern', string)
        if m:
            do_another_thing(m)
        else:
            m = re.match(r'athirdpattern', string)
            if m:
                do_a_third_thing(m)
    

    with this clean chain of tests:

    if m := re.match(r'pattern', string):
        do_thing(m)
    elif m := re.match(r'anotherpattern', string):
        do_another_thing(m)
    elif m := re.match(r'athirdpattern', string):
        do_a_third_thing(m)
    
  2. Reading a file by block, replacing:

    while True:
        block = file.read(4096)
        if not block:
            break
    

    with the clean:

    while block := file.read(4096):
    

Those are useful things people really need to do with some frequency, and even the “canonical” versions I posted are often misimplemented in other ways (e.g. applying the regex test twice to avoid the arrow pattern, duplicating the block = file.read(4096) once before loop and once at the end so you can run while block:, but in exchange now continue doesn’t work properly, and you risk the size of the block changing in one place but not another); the walrus operator allowed for better code.

The listcomp accumulator isn’t (much) better code. itertools.accumulate exists, and even if it didn’t, the problem can be worked around in other ways with simple generator functions or hand-rolled loops. The PEP does describe it as a benefit (it’s why they allowed walrus assignments to “escape” comprehensions), but the discussion relating to this scoping special case was even more divided than the discussion over adding the walrus itself; you can do it, and it’s arguably useful, but it’s not something where you look at the two options and immediately say “Man, if not for the walrus, this would be awful”.

And just to satisfy the folks who want evidence of this, note that they explicitly blocked some use cases that would have worked for free (it took extra work to make the grammar prohibit these usages), specifically to prevent walrus overuse. For example, you can’t do:

x := 1

on a line by itself. There’s no technical reason you can’t, at all, but they intentionally made it a syntax error for the walrus to be used without being wrapped in something. (x := 1) works at top level, but it’s annoying enough no one will ever choose it over x = 1, and that was the goal.

Until someone comes up with a common code pattern for which the lack of :+= makes it infeasibly/unnecessarily ugly (and it would have to be really common and really ugly to justify the punctuation filled monstrosity that is :+=), they won’t consider it.

User contributions licensed under: CC BY-SA
3 People found this is helpful
Advertisement