
The Python walrus operator, written as :=, is one of the most talked-about features introduced in Python 3.8. It lets you assign a value to a variable and use that value in the same expression — something that was not possible before without splitting the logic into two separate lines. If you have ever written a while loop where you had to repeat a function call just to check a condition, the walrus operator is designed exactly for that kind of situation.
The name "walrus" comes from how the symbol looks — := resembles the eyes and tusks of a walrus when you tilt your head sideways. More formally, it is called an assignment expression, and it was introduced through PEP 572.
The walrus operator uses the := symbol and follows this pattern:
variable := expression
You place this inside a larger expression — inside a conditional, a loop, or a comprehension — and the result of the right-hand side gets both assigned to the variable and used in the surrounding expression. This is what makes it different from a regular assignment using =, which is a statement and cannot appear inside an expression.
Here is the simplest possible example to see the difference:
import random
# Without walrus operator
value = random.randint(1, 10)
if value > 5:
print(f"Got {value}, which is greater than 5")
# With walrus operator
if (value := random.randint(1, 10)) > 5:
print(f"Got {value}, which is greater than 5")
In the second version, random.randint(1, 10) is called once, assigned to value, and the result is immediately compared against 5 — all in a single line. After the if block, value remains accessible because walrus operator assignments persist in the surrounding scope.
The most practical use of the Python walrus operator is simplifying while loops that read data or compute a value repeatedly. The traditional pattern requires calling a function twice — once for the condition check and once inside the body — or using a while True loop with a break. The walrus operator eliminates both awkward patterns.
Consider reading chunks from a file. Without the walrus operator:
with open("sample.txt", "w") as f:
f.write("Hello World\nPython is great\nWalrus operator rocks\n")
with open("sample.txt", "r") as f:
chunk = f.readline()
while chunk:
print(chunk.strip())
chunk = f.readline()
Hello World
Python is great
Walrus operator rocks
You have to write chunk = f.readline() twice — once before the loop starts and once at the end of each iteration. With the walrus operator:
with open("sample.txt", "w") as f:
f.write("Hello World\nPython is great\nWalrus operator rocks\n")
with open("sample.txt", "r") as f:
while chunk := f.readline():
print(chunk.strip())
Hello World
Python is great
Walrus operator rocks
The call to f.readline() now lives directly in the while condition. Each time the loop checks the condition, it reads a new line, assigns it to chunk, and uses the result as the truthiness check. When readline() returns an empty string (end of file), the loop exits naturally.
Another place where the Python walrus operator shines is inside if statements where you need to both compute a value and act on it in the same block.
Imagine you are writing a function to find a match in a list using a search function:
def find_admin(users):
return next((u for u in users if u.startswith("admin_")), None)
users = ["alice", "bob", "admin_carlos", "diana"]
# Without walrus operator
match = find_admin(users)
if match:
print(f"Admin found: {match}")
else:
print("No admin user found")
Admin found: admin_carlos
With the walrus operator, you write this as one tighter block:
def find_admin(users):
return next((u for u in users if u.startswith("admin_")), None)
users = ["alice", "bob", "admin_carlos", "diana"]
if match := find_admin(users):
print(f"Admin found: {match}")
else:
print("No admin user found")
Admin found: admin_carlos
The assignment happens inside the if condition, and match is immediately available in both the if and else branches. You avoid the temporary variable setup that would otherwise sit awkwardly above the if.
The walrus operator is especially useful inside list comprehensions when you need to call an expensive function once per item but use its result in both the filter condition and the output value. Without :=, you would either call the function twice or switch to a regular loop.
Suppose you have a list of strings and you want to keep only the ones that have more than 5 characters after stripping whitespace, and you want the stripped version:
raw_strings = [" hello ", " hi ", " python ", " ok ", " walrus "]
# Without walrus operator — strip() is called twice per item
cleaned = [s.strip() for s in raw_strings if len(s.strip()) > 5]
print(cleaned)
['hello', 'python', 'walrus']
With the walrus operator, strip() is only called once:
raw_strings = [" hello ", " hi ", " python ", " ok ", " walrus "]
cleaned = [stripped for s in raw_strings if len(stripped := s.strip()) > 5]
print(cleaned)
['hello', 'python', 'walrus']
Inside the comprehension, stripped := s.strip() assigns the stripped string to stripped as part of evaluating the filter condition, and then stripped is used as the output value. One function call, two uses.
The Python walrus operator pairs naturally with the re module for situations where you search for a pattern and then want to use the match object if it exists. Previously you had to assign the match outside the conditional, then check it separately.
import re
text = "Order number: 48291, placed on 2024-03-15"
# Without walrus operator
match = re.search(r"\d{4}-\d{2}-\d{2}", text)
if match:
print(f"Date found: {match.group()}")
Date found: 2024-03-15
With the walrus operator this becomes cleaner:
import re
text = "Order number: 48291, placed on 2024-03-15"
if match := re.search(r"\d{4}-\d{2}-\d{2}", text):
print(f"Date found: {match.group()}")
Date found: 2024-03-15
The call to re.search() happens once, the result goes into match, and the match object's truthiness (truthy if found, falsy if None) drives the condition — all in one line.
One of the most compelling reasons to use the Python walrus operator is reducing redundant computation in expressions that check a value and then act on it. Consider a pipeline that processes items through a potentially slow transformation:
def transform(n):
return n * n - n + 7
numbers = [1, 5, 3, 8, 2, 9, 4]
# Without walrus — transform() called twice for filtered items
result = [transform(n) for n in numbers if transform(n) > 20]
print(result)
[27, 61, 79]
The function runs twice for every number that passes the filter. With the walrus operator:
def transform(n):
return n * n - n + 7
numbers = [1, 5, 3, 8, 2, 9, 4]
result = [t for n in numbers if (t := transform(n)) > 20]
print(result)
[27, 61, 79]
transform(n) is now called exactly once per item. The result is stored in t, checked against 20, and if the check passes, t is used directly in the output list.
The assignment expression can appear in nested or chained conditions where you would otherwise need multiple temporary variables. Here is an example that processes a list of user inputs, validates each, and reacts to the first invalid one found:
def validate_age(value):
try:
age = int(value)
return age if 0 < age < 130 else None
except ValueError:
return None
inputs = ["25", "abc", "200", "34"]
for raw in inputs:
if (age := validate_age(raw)) is None:
print(f"Invalid input: '{raw}'")
else:
print(f"Valid age: {age}")
Valid age: 25
Invalid input: 'abc'
Invalid input: '200'
Valid age: 34
Each call to validate_age() happens once, the result is assigned to age, and the same value is used in both branches of the conditional. Without := you would need a separate assignment line before each if.
The walrus operator assigns to the nearest enclosing scope that is not a comprehension. This means if you use := inside a list comprehension, the variable leaks into the enclosing function or module scope — unlike the iteration variable of the comprehension itself, which stays local.
data = [1, 4, 9, 16, 25]
squares_over_10 = [y for x in data if (y := x * 2) > 10]
print(squares_over_10)
print(y) # y is accessible here — it holds the last assigned value
[18, 32, 50]
50
This is intentional by design and is what makes the walrus operator useful in comprehensions — but it is worth knowing so you do not accidentally shadow a variable in the outer scope.
This example brings together the walrus operator in a while loop, a list comprehension, and a conditional — simulating a simple interactive number game that collects valid guesses from a list and processes them:
import re
def parse_number(text):
if match := re.fullmatch(r"\d+", text.strip()):
return int(match.group())
return None
def classify(n):
if n % 15 == 0:
return "FizzBuzz"
elif n % 3 == 0:
return "Fizz"
elif n % 5 == 0:
return "Buzz"
return str(n)
raw_inputs = ["15", "7abc", "9", "20", "bad", "45", "11", "30"]
valid_numbers = [num for raw in raw_inputs if (num := parse_number(raw)) is not None]
print("Valid numbers:", valid_numbers)
results = []
index = 0
while index < len(valid_numbers) and (current := valid_numbers[index]):
results.append(f"{current} -> {classify(current)}")
index += 1
for line in results:
print(line)
Valid numbers: [15, 9, 20, 45, 11, 30]
15 -> FizzBuzz
9 -> Fizz
20 -> Buzz
45 -> FizzBuzz
11 -> 11
30 -> FizzBuzz
The list comprehension uses := to call parse_number() once per item and filter out None values in the same pass. The while loop uses := to pull the current number and check it in one expression. The classify() function applies the classic FizzBuzz rules. Every piece of data flows through exactly one computation per step, which is the walrus operator working the way it was designed.