The walrus operator (:=), formally known as an assignment expression, was introduced in Python 3.8 via PEP 572 and quickly became one of the most talked-about features in the language. It lets you assign values to variables inside an expression — something that wasn't possible before.
In this guide, you'll understand exactly what the walrus operator is, how to use it correctly, when it actually simplifies your code, and when you should avoid it.
What Is the Walrus Operator?
The walrus operator (:=) assigns a value to a variable while simultaneously using that value in an expression. It gets its name from the way it looks like the eyes and tusks of a walrus lying on its side.
Basic syntax:
(variable := expression)
Unlike the traditional assignment operator (=), which is a statement and doesn't return a value, the walrus operator is an expression that returns the assigned value. This means you can use it inside other expressions.
# Without walrus operator
n = len(items)
if n > 10:
print(f"The list has {n} items")
With walrus operator
if (n := len(items)) > 10:
print(f"The list has {n} items")
The parentheses around the walrus expression matter. Though not always required, they are strongly recommended by the official Python documentation to avoid ambiguity.
Classic Use Cases
1. While Loops with Assignment
The most common use case is in while loops where you need to read input and check its validity in one line:
# Classic: reading lines from a file
with open("data.txt") as file:
while (line := file.readline()):
print(line.strip())
Before Python 3.8, you had to write:
with open("data.txt") as file:
line = file.readline()
while line:
print(line.strip())
line = file.readline()
The second version duplicates code and is error-prone (forgetting the extra line = file.readline() at the end causes an infinite loop). The walrus operator eliminates this problem entirely.
2. List Comprehensions
In Python functions that get called multiple times inside a comprehension, the walrus operator prevents repeated processing:
# Without walrus: expensive_calc() runs 2x per item when True
bad_results = [expensive_calc(x) for x in data if expensive_calc(x) > 0]
With walrus: expensive_calc() runs 1x per item
results = [val for x in data if (val := expensive_calc(x)) > 0]
This is especially useful in filters that depend on the result of a costly computation. If you're interested in writing cleaner Python, also check our guide on Python type hints, which pair perfectly with assignment expressions.
3. Validation with If
# Regex: capture and check in one go
import re
text = "Contact: [email protected]"
if (match := re.search(r'[\w.+-]+@[\w-]+\.[\w.-]+', text)):
print(f"Email found: {match.group()}")
Dictionary processing
user = {"name": "John", "age": 25}
if (age := user.get("age")) and age >= 18:
print(f"{user['name']} is an adult")
Advanced Use Cases
Walrus in Match Case (Python 3.10+)
Since Python 3.10, the walrus operator works together with structural pattern matching for even more expressive code:
match command.split():
case ["quit" | "exit"]:
print("Shutting down...")
case ["hello" | "hi", (name := str())]:
print(f"Hello, {name}!")
case _:
print("Unknown command")
Walrus with Nested Comprehensions
# Extract values that meet criteria and process them
data = [3, 7, 2, 9, 4, 8, 1, 6]
result = [
(half, double)
for x in data
if (half := x / 2) > 2 and (double := x * 2) < 20
]
print(result) # [(3.5, 6), (4.0, 8)]
Debugging with Walrus
A neat trick is using the walrus operator to inspect intermediate values without refactoring your code:
# Capture intermediate result for debugging
result = [
(debug := x ** 2 + 1)
for x in range(5)
]
print(debug) # Last computed value: 17
Does the Walrus Operator Improve Performance?
The main performance benefit doesn't come from the operator itself, but from eliminating redundant function calls. Consider this:
import time
def heavy_compute(n):
"""Simulates an expensive calculation"""
time.sleep(0.001)
return n ** 2
numbers = list(range(100))
Without walrus: heavy_compute runs 2x per item when True
start = time.time()
old_result = [heavy_compute(n) for n in numbers if heavy_compute(n) > 50]
print(f"Without walrus: {time.time() - start:.3f}s")
With walrus: heavy_compute runs 1x per item
start = time.time()
new_result = [v for n in numbers if (v := heavy_compute(n)) > 50]
print(f"With walrus: {time.time() - start:.3f}s")
The time difference is dramatic, especially for expensive computations. For an in-depth analysis of Python performance, check the official Python performance tips.
When NOT to Use the Walrus Operator
The walrus operator can hurt readability if overused. Here's when to avoid it:
❌ Overly Complex Expressions
# ❌ Avoid: hard to read and understand
if (a := complex_func(b := another_func(c := 10))) > 5:
print(a, b, c)
✅ Prefer: clear and explicit
c = 10
b = another_func(c)
a = complex_func(b)
if a > 5:
print(a, b, c)
❌ Replacing Simple Assignments
# ❌ Unnecessary
if (x := 10) > 5:
print(x)
✅ Better
x = 10
if x > 5:
print(x)
❌ Inside F-Strings (Be Careful!)
# ⚠️ Works but confusing
print(f"{(x := 5)} squared is {x ** 2}")
✅ Clearer
x = 5
print(f"{x} squared is {x ** 2}")
The Community Debate
The walrus operator sparked one of the biggest controversies in Python history. Language creator Guido van Rossum stepped down as BDFL (Benevolent Dictator for Life) in part due to disagreements over PEP 572. Despite the controversy, the proposal was accepted and shipped in Python 3.8.
Arguments in favor:
- Eliminates duplicated code
- Cleaner code for common patterns (while + read, if + assign)
- Prevents errors from forgetting the extra assignment in loops
Arguments against:
- Steeper learning curve for beginners
- Potential for abuse and unreadable code
- Visual similarity with the assignment operator (
=) can be confusing
If you want to dive deep into the technical reasoning, read the full PEP 572.
Variable Scope with Walrus
One important detail is the scope of variables created with :=. Unlike what many think, the assigned variable lives on in the current scope, not just inside the expression:
# The 'match' variable persists after the expression
if (match := re.search(r'\d+', "abc123def")):
print(f"Number found: {match.group()}")
'match' still exists here!
print(match.group()) # Works: prints "123"
In list comprehensions before Python 3.12, the variable leaks out
[item for _ in range(1) if (item := "leaked")]
print(item) # Python < 3.12: "leaked"; Python 3.12+: NameError
This scoping behavior was one of the main criticisms during the walrus operator discussion. For all the technical details, see the scope section of PEP 572 and the official Python FAQ on walrus.
Walrus with Dictionaries and JSON
The operator is extremely handy when processing JSON data and dictionaries, especially when you need to check if a key exists and process its value at the same time:
import json
data = json.loads('{"user": "john", "email": "[email protected]", "age": 28}')
Check and process required fields
if (name := data.get("user")) and (email := data.get("email")):
print(f"Processing: {name} <{email}>")
Batch processing with walrus
users = [
{"name": "John", "points": 150},
{"name": "Jane", "points": 80},
{"name": "Bob", "points": 200},
{"name": None, "points": 0}
]
Filter valid users with high scores
valid = [
{"name": u["name"], "points": u["points"], "tier": "gold" if u["points"] >= 150 else "silver"}
for u in users
if u["name"] and (points := u["points"]) > 50
]
This pattern is common in ETL pipelines and data processing workflows. For more examples, see the Real Python walrus operator tutorial and the GeeksforGeeks reference.
Real-World Examples
Large File Processing
For files too large to fit in memory, combining while with the walrus operator is the ideal approach:
from pathlib import Path
def process_large(filepath: str, chunk: int = 8192) -> int:
total = 0
with Path(filepath).open("rb") as f:
while (data := f.read(chunk)):
total += data.count(b"\n")
return total
Usage
lines = process_large("server_log.txt")
print(f"Total log entries: {lines}")
Real-Time Result Caching
# Simple cache with walrus
def fetch_api_data(url: str) -> dict:
if url not in cache and (response := requests.get(url)).ok:
cache[url] = response.json()
return cache.get(url)
These patterns are widely used in production applications. For more context, check the article on Python 3.9 new features at Real Python, which covers comprehension improvements that benefit walrus usage.
Best Practices
- Always use parentheses around the walrus expression to avoid ambiguity
- Only use it where it truly simplifies the code (while + read, if + regex, comprehensions)
- Never nest multiple walrus operators in the same expression
- Prefer readability over conciseness — clear code beats short code
- Combine with type hints to make code even more expressive
# ✅ Well-written example
import re
from pathlib import Path
def process_log(filepath: str) -> list[str]:
errors: list[str] = []
pattern = re.compile(r'ERROR: (.+)')
with Path(filepath).open() as f:
while (line := f.readline()):
if (match := pattern.search(line)):
errors.append(match.group(1))
return errors</code></pre>
Summary
The walrus operator (:=) is a powerful addition to Python that, when used wisely, makes your code cleaner, more efficient, and more expressive. Key takeaways:
- ✅ Assigns values inside expressions
- ✅ Eliminates duplicated code in while loops and comprehensions
- ✅ Improves performance by preventing redundant calls in filters
- ✅ Available since Python 3.8
- ⚠️ Use sparingly — readability always comes first
To continue learning, explore these official resources:
- What's New in Python 3.8 — official release notes
- Official Python Tutorial — learn Python from scratch
- Real Python: Walrus Operator — in-depth tutorial
Master this tool and your Python code will be more Pythonic, efficient, and elegant!