20.5 The Walrus Operator :=: Assignment Expressions
The walrus operator (:=), formally known as the assignment expression, was introduced in Python 3.8 (PEP 572). Its primary purpose is to enable assignment within an expression context, such as the condition of an if or while statement, or within a list comprehension. This powerful syntactic addition allows you to assign a value to a variable and simultaneously use that value in a surrounding expression, thereby reducing code duplication and often enhancing readability.
Why Use the Walrus Operator?
The core motivation behind the walrus operator is to avoid repetitive calculations or function calls. Before its introduction, a common pattern was to perform an operation, check its result, and then use that result again if the condition was met. This often led to code that either called the same function twice (inefficient) or assigned the value in a separate statement prior to the conditional block (verbose).
Consider this common pre-3.8 pattern for reading data from a file:
# Without Walrus Operator (verbose)
line = file.read()
while line:
process(line)
line = file.read()
The walrus operator condenses this by embedding the assignment (line = file.read()) directly into the while condition (while line:), making the code more concise and easier to follow.
# With Walrus Operator (concise)
while (line := file.read()):
process(line)
Syntax and Basic Usage
The syntax is straightforward: NAME := expr. The operator evaluates the expression on the right-hand side (expr), assigns the result to the variable NAME, and the entire assignment expression itself then evaluates to that same value.
# Assigning and using a value in a single expression
if (n := len([1, 2, 3, 4])) > 3:
print(f"The list is long, with {n} elements.")
In this example, len([1, 2, 3, 4]) is computed, assigned to n, and the value of n (which is 4) is then compared to 3. The print statement can then use the already-assigned variable n.
Use Cases in Control Flow Statements
The walrus operator shines in if and while statements, eliminating the need for an initializing assignment before the loop and a repeated assignment at the end of the loop block.
# Processing user input until 'quit'
while (user_input := input("Enter a value (or 'quit' to stop): ")) != 'quit':
print(f"You entered: {user_input}")
# user_input is available here with its last assigned value
Use Cases in List Comprehensions
This is a powerful application that prevents recalculating an expensive function call within the comprehension’s expression. The value computed in the “condition” part (e.g., a filtering if clause) can be reused directly in the output expression.
# Inefficient way: calling get_data(x) twice for every x
results = [get_data(x) for x in range(10) if get_data(x) is not None]
# Efficient way with walrus: call get_data(x) only once
results = [y for x in range(10) if (y := get_data(x)) is not None]
Parentheses Are Often Mandatory
A critical rule to remember is that an assignment expression must be surrounded by parentheses when used within a larger expression. The := operator has a very low precedence, lower than most comparison and logical operators. Omitting parentheses will lead to a SyntaxError or unintended behavior.
# Correct: Parentheses are required
if (value := some_function()) > 10:
...
# SyntaxError: Missing parentheses
if value := some_function() > 10:
... # This is parsed as 'value := (some_function() > 10)'
Scoping Rules for Assignment Expressions
The walrus operator follows the standard scoping rules of Python. A variable assigned in an expression inside a function is local to that function. Crucially, in comprehensions (list, dict, set, generator), the variable is scoped to the surrounding function, module, or class, not to the comprehension itself. This differs from the assignment in a for loop clause of a comprehension, which is local to the comprehension.
x = "global"
# The walrus variable 'y' leaks into the current scope
comprehension = [y for item in [1, 2] if (y := pow(item, 2))]
print(y) # This will print 4, the last assigned value. May be unexpected!
This behavior can be surprising and is a common pitfall. It’s considered best practice to use unique variable names for walrus assignments to avoid accidentally overwriting an existing variable in the outer scope.
Best Practices and When to Avoid It
The walrus operator is a tool, not a mandate. Its goal is to improve code clarity. Use it when it makes the code more readable by reducing duplication. Avoid using it if it makes a single line overly complex or difficult to understand.
Good Use (Clear and Concise):
if (match := pattern.search(data)) is not None:
print(f"Found match: {match.group(0)}")
Poor Use (Overly Complex):
# Hard to read and debug
print(x := (y := (z := input("Number: ")) + " world") + "!")
A good rule of thumb is that if you find yourself nesting multiple walrus operators on a single line or using it in a way that feels clever but obscure, it’s better to break the statement into multiple, clearer lines of code.