Assignment expressions are a new feature added in Python 3.8. They allow you check a value and assign it to a variable in one expression, simplifying many for and while constructs
The operator is
:= which looks like the eyes and tusks of a walrus, hence its name, the walrus operator.
Here is a simple code fragment that prints the length of a string
s, but only if the string is longer than 5:
if len(s) > 5:
That is ok, but it calls
len(s) twice, which isn't ideal. Repeated code is best avoided where possible from the point of view of readability, and of course it can be inefficient to make unnecessary function calls.
The standard way to avoid this would be:
x = len(s)
if x > 5:
This solves the problem but adds an extra line to your code.
The walrus operator allows you to do this:
if (x := len(s)) > 5:
:= operator assigns the value of
len(s) to the variable
x, but still allows us use the result in the comparison expression.
Until 3.8, assignment in Python could only be done via assignment statements like this:
x = len(s)
But this is a statement, so it cannot be used where an expression is required. In other words, you can’t do this:
if (x = len(s)) > 5: # Invalid syntax!
Assignment expressions can be thought of as being expressions that have the side effect of assigning a variable. In other words:
x := len(s)
is exactly like
len(s), but it also assigns the result of
len(s) to the variable
One thing to bear in mind is that assignment operators don’t allow you to do anything new in Python. There is nothing you can do with the walrus operator that you couldn’t do reasonably easily without it. What it can do in certain circumstances is make your code a little neater and more readable, without needing to resort to repeated calculations.
Another area where assignment expressions can help if with while loops, for example if you are reading blocks of data from a file object
f. Here is how this typically would look:
data = f.read(1024)
data = f.read(1024)
The while loop processes data while ever
read returns a true value. However, to cope with the fact that the file might be empty, we need an extra read before the loop, and then we need to pre-read the next block of data at the end of each loop. It works, but it is a little clunky.
Here is how we would do it with an assignment expression:
while data := f.read(1024):
This is much neater. At the start of each loop we read the data that we are going to use. We store it in
data at the same time as checking if it is false (indicating the end of the file).
In the next example we will use a list comprehension to calculate
sin(x) for each element of an input sequence
s, but we wish to filter out any values that are less than 0. We could do it like this:
v = [math.sin(x) for x in s if math.sin(x) >= 0]
This is not ideal, again because we are calling
sin(x) twice on the same value. This is both bad style and potentially slow since
sin is a relatively expensive function to calculate. Unfortunately, without the walrus operator the only obvious way to fix this was to use two list comprehensions:
temp = [math.sin(x) for x in s]
v = [x for x in temp if x >= 0]
This is also pretty horrible. But here is the solution using a walrus operator:
v = [y for x in s if (y := math.sin(x)) >= 0]
This avoids calling
sin(x) twice, and is arguably more declarative than the original case because it is obvious that you are using the same function for both the test and the value transformation from
Comparison with other languages
Several existing languages already have a similar feature. Perhaps the most well known is C, first implemented in 1972, which has always treated assignments as expressions rather than statements. Of course, there is no shame in pinching a useful feature from one of the classic languages.
So in C, here is a simple assignment statement:
x = 2 + 1;
This statement containing a single expression
x = 2 + 1. The expression takes the value 3, the value assigned. This means you can do something like this:
x = y = z = 2 + 1;
To assign the value 3 to all the variables. The calculation
2 + 1 is only performed once.
Assignment also works with if and while constructs, for example:
if (a = f(x))
This will assign
a. If the result of calling
f(x) is non-zero it will call
perform_action(a). Unlike Python, we don't use a special walrus operator, we just use the normal assignment operator
This can have negative consequences. Suppose we actually wanted to do this:
if (a == f(x))
This code calls
f(x) and checks if its value is equal to
a. It doesn't change
a. It will only call
perform_action(a) if the value of
f(x) is equal to
The problem is that the two code blocks shown are both valid, and both useful. Unfortunately they have very different results, but they only differ in one character
= instead of
==. This is quite a common source of bugs in C.
In Python, the
= operator in a conditional expression would be a syntax error. You can either use
:= for an assignment, or
== for a compare. It is much more difficult to mix the two accidentally.
The walrus operator has a fairly limited purpose — it is intended to be created cleaner code when you have a simple expression that is used in a condition (if or while statement) and is also required to be used again soon afterwards.
It doesn’t do anything that can’t be done in other ways, so the best advice is that if it doesn’t make your code clearer, don’t use it.
Originally published at https://pythoninformer.com.