Topic 2.9
The Walrus Operator :=
In one line
The walrus operator := assigns a value to a name inside an expression, so you can test a value and keep it in one step. It shortens loops that read until a stop signal and filters that need a computed value twice, and it is not worth using when it makes a line harder to read.
Think of it like this
A cashier scanning an item. She reads the price and, in the same moment, writes it on the receipt. She does not scan the item twice, once to look and once to write. The walrus operator is that: compute a value, test it, and keep it, all in one go. (Its name comes from the := shape, which looks like a walrus lying on its side, eyes and tusks.)
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- Walrus operator
- The symbol
:=. It assigns a value to a name inside an expression and gives that value back. - Assignment statement
- A line like
x = 5that stores a value. It cannot be used inside another expression. - Assignment expression
- A piece like
(x := 5)that stores a value and also is that value. - Loop-and-a-half
- A loop that reads a value in the middle and decides to stop, so the read and the test belong together.
- Scope
- Where a name exists. A walrus name belongs to the surrounding function or module, not just the expression.
- Side effect
- A change a piece of code makes besides giving a value, such as
pop(0)removing an item. - pop(0) (preview)
- A list method that removes the first item and returns it. Phase 4 covers list methods.
Step by step
01The problem: two steps for one idea
Both snippets do the same job. The first computes the length on its own line and the second lets you write it inside the condition. The result n is available in the block either way.
text = "supercalifragilistic"
# Without the walrus
n = len(text)
if n > 10:
print(f"{n} characters is too long")
# With the walrus
if (n := len(text)) > 10:
print(f"{n} characters is too long")02Loop-and-a-half
inbox.pop(0) takes the first message out of the list and gives it back. The walrus stores it in message and the loop tests it against "stop". After the loop, the list still holds what was never read: "ignored".
inbox = ["hello", "world", "stop", "ignored"]
while (message := inbox.pop(0)) != "stop":
print("got", message)
print("left in inbox:", inbox)03Keep a lookup result
A dictionary's get returns None when a key is missing (Topic 4.4). The walrus stores the result and the test is not None checks it, with no second lookup. After the test, the block can use price directly.
prices = {"apple": 30, "banana": 12}
if (price := prices.get("apple")) is not None:
print("apple costs", price)04Reuse a value inside a comprehension
A comprehension builds a list in one line (Topic 4.7). The condition squares each x once, keeps the result in sq, tests it, and the part before for collects the same sq. Without := you would compute x * x twice.
data = [3, 8, 1, 9, 4]
big_squares = [sq for x in data if (sq := x * x) > 10]
print(big_squares) # [64, 81, 16]05The name stays alive
The walrus name is not private to the if. After the second check, price still exists and holds None. This is useful but can surprise you when you reuse a name by accident.
prices = {"apple": 30}
if (price := prices.get("kiwi")) is not None:
print("kiwi costs", price)
else:
print("no kiwi")
print("price is now", price) # price is now None06What is not allowed, and the brackets habit
A walrus cannot be a whole statement. Use = for that. Inside a larger expression, put the walrus in brackets, as in y = (z := 5) + 1: z becomes 5 and y becomes 6.
# y := 5 # SyntaxError: invalid syntax
y = (z := 5) + 1
print(y, z) # 6 507When not to use it
If the plain version is just as short, keep it. If you need two walruses in one line or the reader must pause to decode it, split it up. The walrus is a tool for removing repetition, not a style goal.
In this sketch, compute() stands for any function (Phase 5) and ... means "more code here". It is not meant to be run.
# Harder to read than it needs to be
if (a := compute()) and (b := a * 2) > 10:
...
# Clearer
a = compute()
b = a * 2
if a and b > 10:
...Try it yourself
- 1
Remove the walrus
Rewrite the
whileloop in the first example without:=. You will need either a duplicated read or awhile Truewith abreak. Compare the lengths. - 2
Filter with a stored value
Change the comprehension to keep only the squares that are greater than 20. Predict the list, then run (it is
[64, 81]). - 3
See the leak
After the comprehension in the second example, try
print(sq). The namesqis available outside: it holds the last value that was computed.
Code & diagrams
Move "stop" to the front of the list and run it: the loop body never runs. Change the 10 to 25 and watch the if branch switch off.
Expected output
got hello
got world
left in inbox: ['ignored']
20 characters is too longCheck the line "price is now None": the name from the last test is still there. Then try y = (z := 5) + 1 with other numbers.
Expected output
[64, 81, 16]
apple costs 30
no kiwi
price is now None
6 5Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Walrus as a whole statement
Use := where a plain statement is expected.
y := 5
print(y)Myth vs fact
Myth
:= is just another way to write =.
Fact
= is a statement. := is an expression that returns the value, so it can be used inside conditions and comprehensions.
Myth
The walrus name only exists inside the if or the loop.
Fact
It is bound in the surrounding scope and stays after the statement ends.
Myth
More walruses make code more Pythonic.
Fact
It is a tool for removing duplication. Overusing it makes code harder to read.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
Assignment expressions came from PEP 572 and arrived in Python 3.8 after a long debate. The need was real:
while chunk := file.read(8192):is now the standard way to read a file in blocks. - ▸
In a comprehension, a walrus binds the name in the containing function, not in the comprehension's own scope, which is deliberately different from the loop variable. You also cannot rebind the comprehension's own iteration variable with it.
- ▸
any()andall()with a walrus can capture the item that made them stop, a neat trick for "find and keep" in one line:if any((hit := x) > 10 for x in data): print(hit). Use it only when the code stays readable. - ▸
Precedence rules to know:
:=binds more loosely than everything else, so(x := 1, 2)is the tuple(1, 2)withx = 1. It is not allowed unparenthesised at the top of an expression statement, as a keyword-argument value (f(a=x := 1)), or in the iterable part of a comprehension ([y for y in (z := data)]is aSyntaxError). Inside a class body a comprehension cannot use it at all. - ▸
Linters (Ruff) and teams often allow the walrus in
ifandwhileheads and discourage it elsewhere. Watch the Python 3.8 floor: a library that supports 3.7 cannot use it.
Remember this
- 1
You already know the assignment statement
x = 5. Recall from Topic 2.2: a statement does something, an expression gives a value.x = 5stores a value but is not an expression, so it cannot sit inside a condition. The assignment expressionx := 5does both jobs: it stores the value and the whole expression *has* that value. So(n := len(text)) > 10stores the length innand also tests it. - 2
The classic use is to avoid computing something twice. Without it you write
n = len(text)on one line andif n > 10:on the next, or you calllen(text)twice. With it,if (n := len(text)) > 10:computes once, tests, and leavesnready for the block:print(f"{n} characters is too long"). - 3
The second classic use is the loop-and-a-half: read a value, and stop when it is a stop signal.
while (message := inbox.pop(0)) != "stop":takes the next message (pop(0)removes and returns the first item of a list) and loops while it isn't"stop", andmessageis available in the body. The old way needed the read written twice, once before the loop and once at the end of the body, or awhile Truewith abreak. - 4
It also helps when a comprehension must test a computed value and keep it:
[sq for x in data if (sq := x * x) > 10]squares each number once, filters on it, and collects the same value. A comprehension is a one-line way to build a list (Topic 4.7). Without the walrus the square is calculated twice. - 5
Know the rules. A bare
y := 5as a whole statement is aSyntaxError; usey = 5. The expression usually needs brackets (if (n := 5) > 3), and putting them in always is a safe habit. The name belongs to the surrounding code, not just to theif. (Its scope, the place where the name exists, is the whole function or file.) So it stays alive after anifor loop, and even after a comprehension. After the testif (price := prices.get("kiwi")) is not None,priceis still there, holdingNone. (prices.get(...)looks up a key and givesNoneif it is missing; Topic 4.4.) - 6
Use it sparingly. It is worth it when it removes a repeated expensive call or a duplicated read, and when the line stays easy to read. If a reader has to stop and decode it, a plain assignment on its own line is better. Nested or multiple walruses on one line are almost always a mistake.
Explain it without notes
What does the walrus operator do, and how is it different from =?
Show a loop that reads until a stop value, with and without :=.
Why does a name set with := inside an if still exist after the if?
When should you avoid the walrus operator?
Practice
With inbox = ["a", "b", "end", "c"], use a walrus loop with pop(0) to print each message until "end". Print a then b.
Use if (n := len("hello world")) > 5: to print Long text: 11.
Using a comprehension with a walrus, collect the squares greater than 20 from [2, 5, 3, 6] and print [25, 36].
Trade-offs
- ↔
Fewer lines vs readability. The walrus removes duplication but packs more into a line. Use it where the result still reads naturally.
- ↔
Compatibility. Code that uses
:=needs Python 3.8 or newer. Old environments will refuse to run it.
Done when you can
Done when you can explain the difference between
=and:=.Done when you can write a loop-and-a-half with
:=and anifthat keeps a computed value.Done when you can say why the walrus name stays alive after the statement.
Done when you can name the cases where a plain assignment is the better choice.