Assignment expressions in Python

A new version of Python just came out (Python 3.8), and that means we get some new toys to play with. One of the interesting new features to come with Python 3.8 was assignment expressions, and the much debated "walrus operator" (:=). In this post we're going to talk about what assignment expressions are, and how to use them.

Assignment expressions were proposed in PEP 572, which was a hugely controversial proposal. I actually think assignment expressions are a great addition to the language; however, before we show off what they can do, I can't stress enough that they are really easy to abuse. Just pay extra care that you're not making your code harder to read by using them.

So, what exactly is all this fuss about?

Here is a piece of code from our Complete Python Course, from an exercise where we practice basic loops combined with conditionals:

user_input = input('Enter q or p: ')
 
while user_input != 'q':
    if user_input == 'p':
        print("Hello!")

    user_input = input('Enter q or p: ')

The code above is fairly simple. We have this while loop which keeps asking the user to press p or q, and if the user pressed p, we print this cheerful little "Hello!" message. If the user presses q, on the other hand, the loop ends. For any other input, we do nothing.

While this code is okay, we do have this annoying bit of duplication, because we have to start up the loop by defining user_input. If we don't, user_input is undefined during the first iteration of the loop.

Now here is the same code using an assignment expression:

while (user_input := input('Enter q or p: ')) != 'q':
    if user_input == 'p':
        print("Hello!")

As you can see we now have our cute little walrus next to user_input when we define the loop condition, but there's actually quite a lot going on here, so let's break it down.

First of all, what does the := actually do? It's essentially a special type of assignment operator. As usual, we assign some value to a variable name, but the key difference is that the assignment is treated as an expression.

Expressions evaluate to something, and in the case of assignment expressions, the value they evaluate to is the value after the assignment operator, :=.

The fact that the assignment is an treated as an expression is why we can use != 'q' directly after it: the assignment expression represents an actual value. This is different from the assignment statements we're used to in Python, which don't represent any value at all.

As you can see in the example above, we have an expression which forms the condition for our while loop which looks like this:

(user_input := input('Enter q or p: ')) != 'q'

It reads something like, "user_input is not equal to the string 'q', where user_input is the return value of the function call input('Enter p or q: ')".

In the case of our while loop, this expression, including the assignment, is evaluated for every iteration of the loop. This means that for every iteration of the loop, we're going to ask the user to enter a value, and that value will be assigned to the variable user_input, and compared against the string 'q'.

Since user_input is just a normal loop variable, we can still use it inside the loop as well, which is why the if statement isn't complaining.

One important part of the syntax here is that the assignment is wrapped in parentheses. This is a requirement in basically all cases where an assignment expression is likely, but you can read more about it in this section of the PEP.

In addition to trimming off a few lines here and there, assignment expressions can also be used to improve the efficiency of our programs by preventing the repetition of costly operations. I think this is most effective in structures like list comprehensions:

results = [result for x in data if (result := costly_function(x)) >= 0]

Here we're concerned with the values generated by this costly_function when we pass it values from some iterable called data. Instead of calculating the results twice - once when performing the comparison, and once when adding the value to the results list - we now just assign the result to a variable and reference the variable.

The alternative would be something like this:

results = []

for x in data:
    result = costly_function(x)

    if result >= 0:
        results.append(result)

It's quite a lot more verbose, but perhaps that verbosity is worth it for the kind of clarity we get with the traditional for loop. It's really up to you to decide whether or not the assignment expressions are readable enough to justify the reduction in code length.

I'd really recommend taking a look at some of the examples in the PEP, in addition to their advice on using the syntax well. It's really not worth putting assignment expressions all over the place, as it's only going to harm the readability of your code. It's an effective but quite niche tool that we all need to be very careful with.

Wrapping up

That's it for this post on assignment expressions! We'd love to know what you think of the new syntax, so get in touch with us on Twitter, or join us over on Discord.

We'll be adding plenty of content to our blog in the near future covering interesting additions that came with Python 3.8, so make sure you stay up to date with all our content by signing up to our mailing list below. We'll also be adding new lectures to our Complete Python Course as well, so stay tuned!