python-course.eu

5. Assignment Expressions

By Bernd Klein. Last modified: 30 Nov 2023.

Introduction

walrus

This section in our Python tutorial explores the "assignment expression operator" affectionately known to many as "the walrus operator. So, you can call it either the "walrus operator" or the "assignment expression operator," and people in the Python programming community will generally understand what you mean. Despite being introduced in Python version 3.8, we can still view it as a relatively recent addition to the language. The assignment expressions have been discussed in PEP 572 and this is what was written about the naming:

During discussion of this PEP, the operator became informally known as "the walrus operator". The construct's formal name is "Assignment Expressions" (as per the PEP title), but they may also be referred to as "Named Expressions" (e.g. the CPython reference implementation uses that name internally).

The purpose of this feature is not a new way to assign objects to variables, but it gives programmers a convenient way to assign variables in the middle of expressions.

You might still be curious about the origin of the term "walrus operator." It's affectionately named this way because, with a touch of imagination, the operator's characters resemble the eyes and tusks of a walrus.

We will introduce the assignment expression by comparing it first with a simple assignment statement. A simple assignment statement can also be replaced by an assignment expression, even though it looks clumsy and is definitely not the intended use case of it:

x = 5
# can be written as:
(x := 5)  # valid, but not recomended!
# the brackets are crucial

OUTPUT:

5

First you may wonder about the brackets, when you look at the assignment expression. It is not meant as a replacement for the simple "assignment statement". Its primary role is to be utilized within expressions

The following code shows a proper usecase:

x = 4.56
z = (square := x**2) - (6.6 / square)
print(z)

OUTPUT:

20.476194644506

Though you may argue that the following code might be even clearer:

x = 4.56
square = x**2
z = square - (6.6 / square)
print(z)

OUTPUT:

20.476194644506

Here's another Python code example that demonstrates a similar scenario. First with the walrus operator:

# With the walrus operator
n = int(input("Please give a number: "))
if (square := n ** 2) > 3:
    print("The number is greater than 3.")
    print(f"The square of {n} is {square}.")

OUTPUT:

The number is greater than 3.
The square of 42 is 1764.

Now without the walrus operator:

n = int(input("Please give a number: "))
square= n ** 2
if square > 3:
    print("The number is greater than 3.")
    print(f"The square of {n} is {square}.")

OUTPUT:

The number is greater than 3.
The square of 42 is 1764.

Live Python training

instructor-led training course

Enjoying this page? We offer live Python training courses covering the content of this site.

See: Live Python courses overview

Enrol here

Beneficial applications

Now, let's explore some examples where the usage of the assignment expression is really beneficial compared to code not using it.

def f(x):
    return x + 4

numbers = [3, 7, 2, 9, 12]

odd_numbers = [result for x in numbers if (result := f(x)) % 2]
odd_numbers

OUTPUT:

[7, 11, 13]

You can observe that if we choose to avoid using the assignment expression in the list comprehension, we would need to call the function twice. So using the assgment expression makes the code in this context more efficient.

def f(x):
    return x + 4

numbers = [3, 7, 2, 9, 12]

odd_numbers = [f(x) for x in numbers if  f(x) % 2]
odd_numbers

OUTPUT:

[7, 11, 13]

Use Case: Regular Expressions

There is also a great advantage when we use regular expressions. Like in our previous examples we present again two similiar code snippets, the first one without and the second one with the walrus operator:

import re

txt = """The Python training course started at 2022-02-4 
the other one at 2022-01-24
only one date per line, if at all
the dates may also be in this format 2020/10/15
or 20-10-04"""

for line in txt.split('\n'):
    date = re.search(r'(\d{2,4})[-/](\d{2})[-/](\d{2})', line)
    if date:
        year, month, day = date.groups()
        print(f"Found date: {year}-{month}-{day}")

OUTPUT:

Found date: 2022-01-24
Found date: 2020-10-15
Found date: 20-10-04

Now, the code using assignment expressions:

import re

txt = """The Python training course started at 2022-02-4 
the other one at 2022-01-24
only one date per line, if at all
the dates may also be in this format 2020/10/15
or 20-10-04"""

# Split the text into lines and search for dates in each line
for line in txt.split('\n'):
    # Using the assignment expression to find and store the date match
    if (date := re.search(r'(\d{2,4})[-/](\d{2})[-/](\d{2})', line)):
        year, month, day = date.groups()
        print(f"Found date: {year}-{month}-{day}")

OUTPUT:

Found date: 2022-01-24
Found date: 2020-10-15
Found date: 20-10-04

Assignment Expression in File Reading

Fixed-width Files

Files in which each line has the same length are often referred to as "fixed-width" or "fixed-length" files. In these files, each line (or record) is structured so that it contains a predefined number of characters, and the fields within the lines have fixed positions. The files can be read by using the read-method and the walrus operator:

with open('training_course_info.txt') as fh:
    while ((data := fh.read(53)) != ''):
        print(data.rstrip())

OUTPUT:

Python Training Course for Beginners   Berlin     08
Python Intermediate Course             Hamburg    06
Python Advanced Training Course        Frankfurt  08

Now the same in traditional coding style:

with open('training_course_info.txt') as fh:
    data = fh.read(52)
    while data:
        print(data.rstrip())
        data = fh.read(52)

OUTPUT:

Python Training Course for Beginners   Berlin     08

Python Intermediate Course             Hamburg    0
6
Python Advanced Training Course        Frankfurt
08

The benefits for the most recent code snippet are

However, a significant drawback of this approach is that it requires the explicit assignment and reassignment of the data variable, which can be seen as less concise and somewhat less elegant.

Using readline

Most people use a for loop to iterator over a text file line by line. With the walrus operator, we can also elegantly go through a text using the method readline:

word_to_find = "Advanced"
with open("training_course_info.txt") as file:
    while (line := file.readline()):
        if word_to_find in line:
            print(line)

OUTPUT:

Python Advanced Training Course        Frankfurt  08

Code snippet using readline but not the assignment expression:

word_to_find = "Advanced"
with open("training_course_info.txt") as file:
    line = "It shouldn't be empyt that's all"
    while line:
        line = file.readline()
        if word_to_find in line:
            print(line)

OUTPUT:

Python Advanced Training Course        Frankfurt  08

Now with the walrus operator:

word_to_find = "Training"
with open("training_course_info.txt") as file:
    while (line := file.readline()):
        if word_to_find in line:
            print(line)

OUTPUT:

Python Training Course for Beginners   Berlin     08

Python Advanced Training Course        Frankfurt  08

Another Use Case

In the chapter on while loops of our Python Tutorial, we had a little number guessing game:

import random

lower_bound, upper_bound = 1, 20
to_be_guessed = random.randint(lower_bound, upper_bound)
guess = 0
while guess != to_be_guessed:
    guess = int(input("New number: "))
    if guess > to_be_guessed:
        print("Number too large")
    elif guess < to_be_guessed:
        print("Number too small")
else:
    print("Congratulations. You made it!")

OUTPUT:

Number too small
Congratulations. You made it!

As you can see, we had to initialize guess to zero to be able to enter the loop. We can do the initialization directly in the loop condition with an assignment expression and simplify the whole code by this:

import random

lower_bound, upper_bound = 1, 20
to_be_guessed = random.randint(lower_bound, upper_bound)

while (guess := int(input("New number: "))) != to_be_guessed:
    if guess > to_be_guessed:
        print("Number too large")
    elif guess < to_be_guessed:
        print("Number too small")
else:
    print("Congratulations. You made it!")

OUTPUT:

Number too small
Congratulations. You made it!

Critiques of the Walrus Operator in Python

the Python walrus

We said in the beginning of this page that some Python programmers longed for this construct for quite a while. One reason why it was not introduced earlier was the fact that it can also be used to write code which is less readable. In fact, the application of the walrus operator contradicts several principles highlighted in the Zen of Python. To grasp these contraventions, let's delve into the ensuing scenarios.

The Zen of Python says "Explicit is better than implicit". The walrus operator violates this requirement of the Zen of Python. This means that explicit operations are always better than implicit operations. The walrus operator assigns a value to a variable and implicitly returns the value as well. Therefore, it does not align with the concept of "Explicit is better than implicit."

The following code snippet is shows an extreme example which is not recommended to use:

a, b, c = 1, 2, 3
x = 4
y = (c := (a := x*2.3) + (b := x*4.5 -3)) 

What are your thoughts on the following piece of code using the Python assignment operator inside of a print function call?

print((a := x*2.3) + (b := x*4.5 -3) + (x := 4))

OUTPUT:

28.2

These Python code snipets certainly violate two other demands of the Zen of Python:

  1. Beautiful is Better Than Ugly
  2. Complex is Better Than Complicated

The principle "There should be one and preferably only one obvious way to do it" from the Zen of Python emphasizes the importance of having a clear and singular approach. When we have the option to separate assignment and return operations into distinct statements, opting for the less readable and more complex walrus operator contradicts this principle.

z = (x:=3) + (y:=4)
This is the preferred way to do it
x, y = 3, 4
z = x + y

Live Python training

instructor-led training course

Enjoying this page? We offer live Python training courses covering the content of this site.

See: Live Python courses overview

Upcoming online Courses

Enrol here