The walrus operator (:=)

The walrus operator (:=)

A developers guide to writing cleaner Python

Introduction

The walrus operator := officially called the assignment expression operator, was introduced in Python 3.8 PEP 572. The name "walrus" comes from its appearance - if you look at := sideways, it resembles a walrus with tusks.

Operator Visualization

The operator can be visualized as: (variable := expression)
where,
variable = variable name
expression = arithmetic expression,function,container objects : list, sets, or primitive data types : integer, floats, or strings.
(variable:=expression) statement = must be enclosed inside parentheses, else you will get an error.

The operator combines three operations:

  1. Evaluate the expression.

  2. Assign the result to a variable.

  3. Use the assigned value within the same context.

Let's start with a simple example to build intuition:

Examples :

Example 1 - (age := 10)

  1. Evaluate the Expression:

    • The right-hand side of the walrus operator (10) is evaluated first.

    • Result: 10.

  2. Assign the result to a variable:

    • The evaluated result (10) is assigned to the variable age.

    • After this step: age = 10.

  3. Use the assigned value within the same context:

    • The expression (age := 10) returns the value 10, as if it were a function, allowing this value to be used in the same context.

    • Lets use this returned value and pass it as an argument to print function in the same context.

        print(age := 10) # Output : 10
        # print function outputs the value of expression (age := 10), which is now 10.
      
        # Before walrus operator:
        age = 10
        print(age)
      

The walrus operator is effectively a shorthand that does the assignment and returns the value of age.

Example 2 - (age := calculate_age(year))

from datetime import datetime

year = 1980

# Calculate age based on birth year.
def calculate_age(birth_year = 1990):
    current_year = datetime.now().year
    age = current_year - birth_year
    return age if age >= 0 else None

# Without walrus operator
age = calculate_age(year)
if age and age >= 18:
    print(f"Person born in {year} is {age} years old.")

# With walrus operator
if (age := calculate_age(year)) and age >= 18:
        print(f"Person born in {year} is {age} years old.")

Let’s use the same analogy to dry run this code snippet.

Expression : (age := calculate_age(year))

  1. Evaluate the Expression:

    • The right-hand side of the walrus operator, calculate_age(year) is evaluated first.

    • As calculate_age(year) is a function that calculates a person's age based on the provided birth year.

    • Result: 45 , if year = 1980

  2. Assign the result to a variable:

    • The evaluated result (45) is assigned to the variable age.

    • After this step: age = 45.

  3. Use the assigned value within the same context:

    • After the assignment, the entire expression (age := calculate_age(year)) evaluates to the value of age (in this case, 45).

Lets use this returned value: age and use it in the if statement in the same context:
if (age := calculate_age(year)) and age >= 18:
The and operator checks the second condition: age >= 18
Since 45 >= 18 is True, the condition passes, and the body of the if statement is executed printing the message in the console.

Good use cases for the walrus operator

  1. Regular Expression Matching
# Example - Extracting matched substrings
if (match := re.search(r'pattern', text)):
    print(f"Found: {match.group()}")

Concrete example:

import re

text = "Email to pattern match is ishwor@gmail.com"

# Before
match = re.search(r'\w+@\w+\.\w+', text)
if match:
    print(f"Email matched : {match.group()}")  # Output : Email matched : ishwor@gmail.com

# After
if (match := re.search(r'\w+@\w+\.\w+', text)):
    print(f"Email matched : {match.group()}")  # Output : Email matched : ishwor@gmail.com
  1. Reading Files
# Example - Reading file line by line
with open("sample_file.txt") as file:
    while (line := file.readline().strip()):
        print(line)

Concrete example:

# Before
with open("sample_file.txt") as file:
    while True:
        line = file.readline().strip()
        if not line:
            break
        print(line)

# After
with open("sample_file.txt") as file:
    while (line := file.readline().strip()):
        print(line)
  1. Input Validation and Processing
# Example - Reading input from the user
while (user_input := input("Enter a command (or 'q'): ")) != "q":
    print(f"You entered: {user_input}")

Concrete example:

# Before
while True:
    user_input = input("Enter a command (or 'q'): ")
    if user_input == "q":
        break
    print(f"You entered: {user_input}")

# After
while (user_input := input("Enter a command (or 'q'): ")) != "q":
    print(f"You entered: {user_input}")
  1. List Comprehension
# Example - create a list of squares of even numbers 
numbers = [1, 2, 3, 4, 5, 6]
squares = [square for n in numbers if (square := n ** 2) % 2 == 0]
print(squares)  # Output: [4, 16, 36]

Concrete example:

# Before
numbers = [1, 2, 3, 4, 5, 6]
squares = []
for n in numbers:
    square = n ** 2
    if square % 2 == 0:
        squares.append(square)
print(squares)  # Output: [4, 16, 36]

# After
numbers = [1, 2, 3, 4, 5, 6]
squares = [square for n in numbers if (square := n ** 2) % 2 == 0]
print(squares)  # Output: [4, 16, 36]
  1. API Response Processing or Data Fetching
# Example -  simplify the flow of an HTTP request and subsequent data processing
if (response := requests.get(url)).status_code == 200:
    process_data(response.json())

Concrete example:

# Before 
response = requests.get(url)
if response.status_code == 200:
    data = response.json()
    print(f"Successfully fetched {len(data)} items")
    process_data(data)
elif response.status_code == 404:
    print(f"Resource not found: {url}")
else:
    print(f"Error: Status code {response.status_code}")

# After
if (response := requests.get(url)).status_code == 200:
    data = response.json()
    print(f"Successfully fetched {len(data)} items")
    process_data(data)
elif response.status_code == 404:
    print(f"Resource not found: {url}")
else:
    print(f"Error: Status code {response.status_code}")

When Not to Use the Walrus Operator

Here are cases where you should AVOID using the walrus operator.

  1. Simple Assignments
# Bad
if (x := 5) > 0:
    print(x)

# Better
x = 5
if x > 0:
    print(x)
  1. When Readability Matters
# Bad - convoluted logic
if (match := re.search(r"\d+", text)) and match.group(0).isdigit():
    print(match.group(0))

# Better - readability
match = re.search(r"\d+", text)
if match and match.group(0).isdigit():
    print(match.group(0))

Concrete example:

import re

# Bad
text = "The price is $123 dollars"
if (match := re.search(r"\d+", text)) and match.group(0).isdigit():
    number = int(match.group(0))
    if 0 <= number <= 1000:
        print(f"Valid price found: ${number}") # Output : Valid price found: $123
    else:
        print("Price out of valid range")


# Better
text = "The price is $123 dollars"
match = re.search(r"\d+", text)
if match and match.group(0).isdigit():
    number = int(match.group(0))
    if 0 <= number <= 1000:
        print(f"Valid price found: ${number}")  # Output : Valid price found: $123
    else:
        print("Price out of valid range")
  1. Nested Walrus Operator
# Bad - nested walrus operator
if (a := (b := (c := 8) + 2) + 3) > 10:
    print(a, b, c)      # Output : 13 10 8

# Better
c = 8
b = c + 2
a = b + 3
if a > 10:
    print(a, b, c)    # Output : 13 10 8
  1. When the Expression Is Already Simple
# Bad - overcomplicating
while (count := len(items)) > 0:
    items.pop()

# Better
while items:
    items.pop()

Concrete example:

# Bad - overcomplicating
items = [1, 2, 3, 4, 5]
while (count := len(items)) > 0:
    items.pop()

# Better
items = [1, 2, 3, 4, 5]
while items:
    items.pop()
  1. When Variable Scope Isn't Clear
# Bad - unclear scope
[print(x) for x in range(5) if (y := x * 2) > 5]
# bug prone - y's scope might surprise developers
print(y)     # Output : 3
             #        : 4 
             #        : 8

# Better
results = []
for x in range(5):
    y = x * 2
    if y > 5:
        results.append(y)
print(results)  # Output : [6, 8]

Article Summary & My Thoughts:

The key to using the walrus operator effectively is to use it when it genuinely reduces code duplication or makes the code more readable, not just to make the code more compact. If you often need to debug your code or re-read a line several times to understand it, it's best that we avoid using the walrus operator in those situations.

References