Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to rewrite this simple loop using assignment expressions introduced in Python 3.8 alpha?

It appears to me that it is not that straight forward to interchange classic while loops with assignment-expressions-loops keeping the code looking great.

Consider example1:

>>> a = 0
>>> while (a := a+1) < 10:
...     print(a)
... 
1
2
3
4
5
6
7
8
9

and example2:

>>> a = 0
>>> while a < 10:
...     print(a)
...     a += 1
... 
0
1
2
3
4
5
6
7
8
9

How would you modify example1 in order to have the same output (not skipping the 0) of example2? (without changing a = 0, of course)

like image 958
alec_djinn Avatar asked Mar 22 '19 17:03

alec_djinn


People also ask

What is an assignment expression in Python?

An assignment expression is a new syntax introduced in Python 3.8. It is commonly known as the walrus operator ( := a colon followed by an equal sign). The walrus operator assigns a value to a variable but acts as an expression instead of an assignment. The best way to understand the usage of the walrus operator is through an example:

How to evaluate an expression in Python?

In any programming language, an expression is evaluated as per the precedence of its operators. So that if there is more than one operator in an expression, their precedence decides which operation will be performed first. We have many different types of expressions in Python. Let’s discuss all types along with some exemplar codes : 1.

What are arithmetic expressions in Python?

Arithmetic Expressions: An arithmetic expression is a combination of numeric values, operators, and sometimes parenthesis. The result of this type of expression is also a numeric value. The operators used in these expressions are arithmetic operators like addition, subtraction, etc. Here are some arithmetic operators in Python:

What does the assignment expression operator do?

In the examples you’ve seen so far, the := assignment expression operator does essentially the same job as the = assignment operator in your old code. You’ve seen how to simplify code, and now you’ll learn about a different type of use case that’s made possible by this new operator.


2 Answers

Simple loops like your example should not be using assignment expressions. The PEP has a Style guide recommendations section that you should heed:

  1. If either assignment statements or assignment expressions can be used, prefer statements; they are a clear declaration of intent.
  2. If using assignment expressions would lead to ambiguity about execution order, restructure it to use statements instead.

Simple loops should be implemented using iterables and for, they are much more clearly intended to loop until the iterator is done. For your example, the iterable of choice would be range():

for a in range(10):
    # ...

which is far cleaner and concise and readable than, say

a = -1
while (a := a + 1) < 10:
    # ...

The above requires extra scrutiny to figure out that in the loop a will start at 0, not at -1.

The bottom line is that you should not be tempted to 'find ways to use assignment statements'. Use an assignment statement only if it makes code simpler, not more complex. There is no good way to make your while loop simpler than a for loop here.

Your attempts at rewriting a simple loop are also echoed in the Tim Peters's findings appendix, which quotes Tim Peters on the subject of style and assignment expressions. Tim Peters is the author of the Zen of Python (among many other great contributions to Python and software engineering as a whole), so his words should carry some extra weight:

In other cases, combining related logic made it harder to understand, such as rewriting:

while True:
    old = total
    total += term
    if old == total:
        return total
    term *= mx2 / (i*(i+1))
    i += 2

as the briefer:

while total != (total := total + term):
    term *= mx2 / (i*(i+1))
    i += 2
return total

The while test there is too subtle, crucially relying on strict left-to-right evaluation in a non-short-circuiting or method-chaining context. My brain isn't wired that way.

Bold emphasis mine.

A much better use-case for assignment expressions is the assigment-then-test pattern, especially when multiple tests need to take place that try out successive objects. Tim's essay quotes an example given by Kirill Balunov, from the standard library, which actually benefits from the new syntax. The copy.copy() function has to find a suitable hook method to create a copy of a custom object:

reductor = dispatch_table.get(cls)
if reductor:
    rv = reductor(x)
else:
    reductor = getattr(x, "__reduce_ex__", None)
    if reductor:
        rv = reductor(4)
    else:
        reductor = getattr(x, "__reduce__", None)
        if reductor:
            rv = reductor()
        else:
            raise Error("un(shallow)copyable object of type %s" % cls)

The indentation here is the result of nested if statements because Python doesn't give us a nicer syntax to test different options until one is found, and at the same time assigns the selected option to a variable (you can't cleanly use a loop here as not all tests are for attribute names).

But an assignment expression lets you use a flat if / elif / else structure:

if reductor := dispatch_table.get(cls):
    rv = reductor(x)
elif reductor := getattr(x, "__reduce_ex__", None):
    rv = reductor(4)
elif reductor := getattr(x, "__reduce__", None):
    rv = reductor()
else:
    raise Error("un(shallow)copyable object of type %s" % cls)

Those 8 lines are a lot cleaner and easier to follow (in my mind) than the current 13.

Another often-cited good use-case is the if there is a matching object after filtering, do something with that object, which currently requires a next() function with a generator expression, a default fallback value, and an if test:

found = next((ob for ob in iterable if ob.some_test(arg)), None)
if found is not None:
    # do something with 'found'

which you can clean up a lot with the any() function

if any((found := ob).some_test(arg) for ob in iterable):
    # do something with 'found'
like image 56
Martijn Pieters Avatar answered Oct 21 '22 00:10

Martijn Pieters


The problem with the question is, the entire approach to the problem appears to be coming from a programmer trying to use Python like other languages.

An experienced Python programmer wouldn't use a while loop in that case. They'd either do this instead:

from itertools import takewhile, count

for a in takewhile(lambda x: x<10, count()):
    print (a)

...or even simpler:

for a in range(10):
    print (a)

As is so often the case (not always of course), the ugly code presented in the question is a symptom of using the language in a less than optimal way.

like image 29
Rick supports Monica Avatar answered Oct 21 '22 01:10

Rick supports Monica