Python Walrus Operator := Use Cases and Examples
Learn the Python walrus operator (:= assignment expression) with practical examples for loops, comprehensions, conditionals, and when to avoid it.
What you'll learn
- ✓Understand what the walrus operator does and why it exists
- ✓Use := in while loops, if statements, and comprehensions
- ✓Recognize when the walrus operator improves readability
- ✓Avoid common misuses that make code harder to read
Prerequisites
- •Python basics: variables, loops, conditionals
The walrus operator (:=), introduced in Python 3.8, lets you assign a value to a variable as part of an expression. Its official name is “assignment expression.” It solves one specific problem: when you need to both compute a value and use it in the same expression, eliminating duplicate calls or awkward variable placement. This guide shows where it shines and where it does not belong.
What the Walrus Operator Does
A regular assignment is a statement. It cannot appear inside an expression:
# This is a statement (cannot be used inside if, while, etc.)
result = expensive_function()
if result:
print(result)
The walrus operator turns the assignment into an expression that can appear inside if, while, list comprehensions, and other contexts:
# Assignment AND use in one expression
if (result := expensive_function()):
print(result)
The parentheses are often required to disambiguate from other syntax. The variable result is assigned the return value of expensive_function(), and that same value is tested by the if statement.
While Loops: The Classic Use Case
The most compelling use of := is in while loops that read data until a sentinel value:
Without the Walrus Operator
# Read lines until empty
line = input("Enter text (empty to quit): ")
while line:
print(f"You said: {line}")
line = input("Enter text (empty to quit): ")
Notice the duplication: line = input(...) appears twice, once before the loop and once at the end. If you change the prompt, you need to change it in two places.
With the Walrus Operator
while (line := input("Enter text (empty to quit): ")):
print(f"You said: {line}")
One line of code, no duplication. The input is read, assigned to line, and checked for truthiness all in the while condition.
Reading Files in Chunks
# Without walrus
with open("large_file.bin", "rb") as f:
chunk = f.read(8192)
while chunk:
process(chunk)
chunk = f.read(8192)
# With walrus
with open("large_file.bin", "rb") as f:
while (chunk := f.read(8192)):
process(chunk)
This pattern eliminates the priming read before the loop.
Socket Programming
import socket
def receive_data(sock: socket.socket) -> str:
chunks = []
while (data := sock.recv(4096)):
chunks.append(data)
return b"".join(chunks).decode()
If Statements: Compute and Check
Use := when you need to compute a value, check a condition on it, and then use it:
import re
text = "Order #12345 confirmed"
# Without walrus
match = re.search(r"#(\d+)", text)
if match:
order_id = match.group(1)
print(f"Found order: {order_id}")
# With walrus
if (match := re.search(r"#(\d+)", text)):
print(f"Found order: {match.group(1)}")
Chained Conditions
The walrus operator is especially useful in if/elif chains where each branch tries a different match:
import re
def parse_command(text: str) -> dict:
if (m := re.match(r"add (\w+) (\d+)", text)):
return {"action": "add", "item": m.group(1), "qty": int(m.group(2))}
elif (m := re.match(r"remove (\w+)", text)):
return {"action": "remove", "item": m.group(1)}
elif (m := re.match(r"list (\w+)", text)):
return {"action": "list", "category": m.group(1)}
else:
return {"action": "unknown", "raw": text}
print(parse_command("add widget 5"))
# {'action': 'add', 'item': 'widget', 'qty': 5}
Without :=, you would need a temporary variable before each elif, making the code much longer.
List Comprehensions: Filter and Transform
The walrus operator prevents redundant computation in comprehensions:
# Without walrus: compute_score is called TWICE per item
results = [
compute_score(item)
for item in items
if compute_score(item) > threshold
]
# With walrus: compute_score is called ONCE per item
results = [
score
for item in items
if (score := compute_score(item)) > threshold
]
This is a significant optimization when compute_score is expensive (database queries, API calls, complex math).
Filtering with Transformation
import json
raw_lines = [
'{"name": "Alice", "age": 30}',
'not json',
'{"name": "Bob", "age": 25}',
'{"name": "Charlie", "age": 35}',
'also not json',
]
def try_parse(line: str) -> dict | None:
try:
return json.loads(line)
except json.JSONDecodeError:
return None
# Parse and filter in one pass
valid_records = [
parsed
for line in raw_lines
if (parsed := try_parse(line)) is not None
]
print(valid_records)
# [{'name': 'Alice', 'age': 30}, {'name': 'Bob', 'age': 25}, ...]
Dictionary Comprehensions
data = {"a": "1", "b": "not_a_number", "c": "3", "d": "4"}
def safe_int(s: str) -> int | None:
try:
return int(s)
except ValueError:
return None
# Only include keys where the value is a valid integer
valid = {
k: parsed
for k, v in data.items()
if (parsed := safe_int(v)) is not None
}
print(valid) # {'a': 1, 'c': 3, 'd': 4}
Any() and All() with Early Results
Combine := with any() to find the first matching element:
names = ["Alice", "Bob", "Charlie", "David"]
# Find the first name longer than 5 characters
if any((match := name) for name in names if len(name) > 5):
print(f"Found: {match}") # Found: Charlie
else:
print("No match")
A cleaner version using a more explicit pattern:
numbers = [1, 5, 3, 8, 2, 9, 4]
# Find the first number greater than 7
found = None
if any((found := n) for n in numbers if n > 7):
print(f"First > 7: {found}") # First > 7: 8
Nested Function Calls
The walrus operator can reduce deeply nested function calls:
# Without walrus: deeply nested or multi-line
import os
# Nested
config_path = os.environ.get("CONFIG_PATH") or os.path.join(
os.environ.get("HOME", "/root"), ".config", "app.toml"
)
# With walrus: clearer flow
if (path := os.environ.get("CONFIG_PATH")):
config_path = path
elif (home := os.environ.get("HOME")):
config_path = os.path.join(home, ".config", "app.toml")
else:
config_path = "/root/.config/app.toml"
When NOT to Use the Walrus Operator
The walrus operator can hurt readability when overused. Here are cases where you should avoid it.
Simple Assignments
# Bad: adds complexity for no benefit
y := f(x) # SyntaxError anyway -- walrus can't replace regular assignment
# Also bad: unnecessary walrus in a simple case
if (x := 10) > 5:
print(x)
# Better: just use regular assignment
x = 10
if x > 5:
print(x)
Multiple Walrus Operators in One Expression
# Bad: hard to follow execution order
result = [(y := f(x), x / y) for x in range(1, 10) if (y := g(x)) > 0]
# Better: use a regular loop
result = []
for x in range(1, 10):
y = g(x)
if y > 0:
result.append((f(x), x / y))
Overly Clever One-Liners
# Bad: "look how smart I am" code
filtered = [y for x in data if (y := transform(x)) and validate(y) and (z := score(y)) > 0.5]
# Better: explicit and readable
filtered = []
for x in data:
y = transform(x)
if y and validate(y) and score(y) > 0.5:
filtered.append(y)
Scope Rules
The walrus operator creates variables in the enclosing scope, not a local scope within the expression:
# The variable 'last' persists after the comprehension
numbers = [1, 2, 3, 4, 5]
squared = [last := x ** 2 for x in numbers]
print(last) # 25 -- the last value assigned
# This is different from regular comprehension variables
# which are scoped to the comprehension:
cubed = [x ** 3 for x in numbers]
# 'x' from the comprehension is not accessible in Python 3
In if and while statements, the walrus-assigned variable lives in the surrounding function scope, as you would expect.
Real-World Example: CLI Argument Processing
import sys
def process_args(args: list[str]) -> dict[str, str]:
config: dict[str, str] = {}
i = 0
while i < len(args):
arg = args[i]
if (m := arg.startswith("--")):
key = arg[2:]
if i + 1 < len(args) and not args[i + 1].startswith("--"):
config[key] = args[i + 1]
i += 2
else:
config[key] = "true"
i += 1
else:
i += 1
return config
# Parse: --verbose --output results.csv --debug
result = process_args(["--verbose", "--output", "results.csv", "--debug"])
print(result) # {'verbose': 'true', 'output': 'results.csv', 'debug': 'true'}
Wrapping Up
The walrus operator solves a narrow but common problem: computing a value and using it in the same expression. Its best use cases are while loops that read until a sentinel, if/elif chains that try multiple matches, and comprehensions that need to filter and transform without redundant computation. The rule of thumb is simple: if := eliminates a duplicated function call or makes a loop cleaner, use it. If it makes a reader pause to untangle the expression, use a regular assignment on its own line instead. Readability counts.
Related articles
- Python Python Syntax and Indentation Explained
Understand Python's syntax rules, the role of indentation, statements, comments, and the most common syntax errors that trip up new developers.
- Python Python Concurrency: asyncio vs threading vs multiprocessing
Compare Python's concurrency models side by side. Learn when to use asyncio, threading, or multiprocessing with practical benchmarks and real-world examples.
- Python Python Environments: venv, pyenv, and Poetry Guide
Master Python environment management with venv for virtual environments, pyenv for version switching, and Poetry for dependency management.
- Python Python Match Statement: Structural Pattern Matching
Master Python's match statement with structural pattern matching. Learn literal, sequence, mapping, class, guard, and OR patterns with practical examples.