The walrus operator (:=) lets you assign a value to a variable inside an expression. That one capability unlocks tighter loops, cleaner comprehensions, and conditionals that do not repeat themselves.
What's in this Python Tutorial▼
- What the Walrus Operator Is and Where It Came From
- Deeper Solutions: Where the Walrus Operator Helps Most
- Check Your Understanding: Patterns
- Under the Hood: What CPython Actually Does
- Spot the Bug: File Reading
- Spot the Bug: Scope Leak
- The Operator That Changed Python's Governance
- Check Your Understanding: History
- Walrus Operator Limitations and Pitfalls
- Spot the Bug: Input Loop
- How to Use the Walrus Operator in Python
- Python Learning Summary Points
- Check Your Understanding: Quiz
- Frequently Asked Questions
- Final Exam & Certificate
Python 3.8 shipped in October 2019 and brought a small but consequential addition: the assignment expression, proposed through PEP 572. The PEP was originally submitted by Chris Angelico in February 2018, later co-authored with Tim Peters and Guido van Rossum, and accepted by Guido in July 2018. The CPython implementation was contributed by Emily Morehouse. Most developers know it by its nickname. The := symbol looks like a walrus face on its side — two eyes and two tusks — and the name stuck. Understanding it takes about five minutes. Using it well takes a little practice. This is one of many Python tutorials on PythonCodeCrack covering operators, expressions, and real-world patterns.
What the Walrus Operator Is and Where It Came From
Before Python 3.8, assignment was always a statement. Statements do not produce a value, which means you cannot embed them inside an expression. If you needed to compute something, store it, and then test it, those had to be separate lines.
# Traditional two-step: compute, then test
data = fetch_data()
if data:
process(data)
PEP 572 introduced a new type of expression — the named expression — using the := operator. It assigns a value to a variable and simultaneously evaluates to that same value. The computation and the binding happen in a single pass.
# Walrus operator: assign and test in one expression
if data := fetch_data():
process(data)
The value returned by fetch_data() is bound to data and then immediately evaluated by the if statement. The variable data is available in the block that follows without any extra setup.
The walrus operator requires Python 3.8 or later. If your code needs to run on Python 3.7 or below, you cannot use :=. Check your version with python --version before adopting it in a shared codebase.
Build the correct walrus operator statement that reads a line from a file and checks it is not empty, in one expression:
while (line := f.readline()):. The parentheses around the walrus expression are required here to avoid a syntax error. f.readline() returns an empty string at end-of-file, which is falsy, so the loop exits cleanly. Using for or plain = would not produce the same assign-and-test behavior.
Deeper Solutions: Where the Walrus Operator Helps Most
The operator is most useful in several recurring patterns. The three that appear in most introductions — while-loop reads, comprehension filters, and regex conditionals — are the starting point. But the operator's real depth shows in patterns that are less commonly discussed: short-circuit accumulation, multi-condition chains, generator pipelines, and network polling loops where the assignment and the test genuinely cannot be separated cleanly.
While loops that read or poll
The classic use case is reading from a stream in a loop. Without :=, you typically need to prime the loop with an initial read before the condition and then read again at the bottom of the body.
# Without walrus — the "read-ahead" pattern (binary chunked read)
with open("data.bin", "rb") as f:
chunk = f.read(4096)
while chunk:
process(chunk)
chunk = f.read(4096) # duplicated call
The := operator collapses the repeated call into the loop condition itself. This pattern is particularly suited to binary or fixed-chunk reads; for iterating over lines in a text file, for line in f: remains the Pythonic choice.
# With walrus — single call, no duplication (binary chunked read)
with open("data.bin", "rb") as f:
while chunk := f.read(4096):
process(chunk)
The same pattern applies directly to network socket polling, where recv() returns an empty bytes object to signal a closed connection.
import socket
HOST, PORT = "127.0.0.1", 9000
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect((HOST, PORT))
# recv() returns b'' when the remote end closes the connection
while data := s.recv(4096):
handle(data)
When the walrus expression is the entire condition of a while or if, parentheses are optional but can improve readability. Write while (chunk := f.read(4096)): if your team finds that clearer than the bare form.
List comprehensions with expensive filters
A common pain point in comprehensions is needing to call the same function twice: once to filter and once to include the result in the output list. The walrus operator solves this by binding the result inside the filter clause.
# Without walrus — process() called twice per item
results = [process(x) for x in data if process(x) is not None]
# With walrus — process() called once per item, result reused
results = [y for x in data if (y := process(x)) is not None]
Regex and conditional expressions
Regex matching is a textbook case. Without the operator, you match, store the result, and then check it in a separate statement. With :=, the match and the check collapse into one.
import re
# Without walrus
m = re.search(r"\d+", line)
if m:
print(m.group())
# With walrus — match object available immediately inside the block
if m := re.search(r"\d+", line):
print(m.group())
Short-circuit accumulation with any() and all()
A less obvious but genuinely useful pattern is combining := with any() or all() to capture the first value that satisfies a condition without re-scanning the iterable. Without the walrus operator, capturing which element triggered the short-circuit requires a separate loop or next() call.
candidates = ["", None, 0, "first_valid", "second_valid"]
# Capture the first truthy value while short-circuiting
if any((result := item) for item in candidates if item):
print(f"First valid candidate: {result}")
# Output: First valid candidate: first_valid
This is meaningfully different from next(filter(None, candidates), None) because it integrates the binding directly into the condition rather than requiring a generator expression plus next() with a default. When the predicate is more complex than a truthiness check, the walrus version scales more cleanly.
Multi-condition chains without redundant calls
When an if block depends on multiple conditions where each depends on the result of the previous, the walrus operator can bind intermediate values so that later conditions can reference them without recalculating.
import json
raw = get_payload() # returns bytes or None
# Three conditions, each depending on the last — no repeated calls
if (
(text := raw.decode("utf-8") if raw else None)
and (obj := json.loads(text) if text else None)
and isinstance(obj, dict)
):
process_record(obj)
Without :=, this requires three separate assignment statements before the if, or nested if blocks that push the actual logic further right. The walrus form keeps the dependency chain visible at the condition level.
Generator expressions as lazy pipelines
Generator expressions evaluate lazily, which means they are a natural fit for the walrus operator when you need to pull values through a transformation, filter, and bind the transformed result for use downstream — all without materializing the intermediate list.
import math
measurements = [4.1, -1.2, 9.5, 0.0, 3.8, -0.5, 7.7]
# Lazy pipeline: transform, filter, bind — no intermediate list
valid_roots = (
root
for x in measurements
if x > 0 and (root := math.sqrt(x)) > 2.0
)
for r in valid_roots:
print(f"{r:.4f}")
The root := math.sqrt(x) binding avoids calling sqrt twice (once to check the threshold, once to get the output value). Because this is a generator expression rather than a list comprehension, no results are computed until iteration begins — which matters when measurements is large or produced by a stream.
Database cursor iteration without materializing the result set
Fetching rows from a database cursor in a loop is a pattern that appears in nearly every production Python application, yet most tutorials do not demonstrate how := interacts with it. When a cursor does not support the iterator protocol directly — or when you want to enforce a row limit while streaming — the walrus form gives you precise control without the overhead of fetchall(), which materializes the entire result set into memory at once.
import sqlite3
conn = sqlite3.connect("metrics.db")
cursor = conn.cursor()
cursor.execute("SELECT event_id, payload FROM events ORDER BY ts")
# Stream rows one at a time — no fetchall(), no intermediate list
while row := cursor.fetchone():
event_id, payload = row
process(event_id, payload)
conn.close()
fetchone() returns None when no more rows remain, which is falsy, so the loop exits cleanly. The same pattern applies to any DB-API 2.0 compliant cursor. The structural advantage over a for row in cursor: loop is that you retain an explicit reference to row after the loop body executes — useful when the last processed row needs to be inspected for error reporting or audit logging without storing a separate variable before the loop.
Stateful token parsing without a class
Parsers that walk a list of tokens and need to both consume a token and branch on its type typically use a class or a set of local variables managed across a loop. The walrus operator enables a compact inline-dispatch pattern that avoids both, keeping the grammar rules visible at the condition level rather than buried in method bodies.
import re
# Tokenizer: returns next (type, value) tuple or None at end of input
def next_token(source, pos):
patterns = [
("NUMBER", r"\d+"),
("IDENT", r"[a-zA-Z_]\w*"),
("OP", r"[+\-*/=]"),
("WS", r"\s+"),
]
for kind, pattern in patterns:
if m := re.match(pattern, source[pos:]):
return kind, m.group(), pos + len(m.group())
return None
# Walk the token stream without storing a state variable before the loop
source = "result = x + 42"
pos = 0
while token := next_token(source, pos):
kind, value, pos = token
if kind != "WS":
print(f"{kind:8} {value!r}")
Each call to next_token() returns the current token and the advanced position in one tuple, or None at end-of-input. The walrus binding makes both available immediately inside the loop body. Without :=, this requires either a sentinel-primed pattern or an inner if token is None: break that separates the termination logic from the condition — making the grammar structure harder to follow at a glance.
Retry loops with exponential backoff
Retry logic is another pattern where the standard approach tends to use a counter variable initialized before the loop. The walrus operator lets you express a bounded retry loop where both the attempt result and the attempt count are bound in the condition, without any pre-loop setup beyond an initial state.
import time, random
def attempt_request(url):
"""Simulates a flaky HTTP call — returns response or None on failure."""
return None if random.random() < 0.7 else {"status": 200, "body": "ok"}
MAX_RETRIES = 5
delays = (2 ** i for i in range(MAX_RETRIES)) # 1, 2, 4, 8, 16 seconds
for delay in delays:
if response := attempt_request("https://api.example.com/data"):
print(f"Success: {response}")
break
print(f"Attempt failed — retrying in {delay}s")
time.sleep(delay)
else:
raise RuntimeError("All retry attempts exhausted")
The walrus binding inside the if means the successful response object is available immediately inside the if block for logging, validation, or processing — with no intermediate variable outside the loop. The for...else construct ensures the RuntimeError fires only when all attempts are exhausted without a successful bind. This is a structural improvement over the common pattern of setting a response = None sentinel before the loop and then checking it after.
- = (regular assignment)
- Statement — does not produce a value and cannot appear inside an expression.
- := (walrus operator)
- Expression — assigns a value and returns it, so it can appear inside conditions, comprehensions, and other expressions.
- = (regular assignment)
- Scoped to the enclosing function or module, as expected.
- := (walrus operator)
- Also scoped to the enclosing function or module — not to the comprehension or lambda where it appears. Variables bound by := inside a comprehension are visible in the outer scope.
- = (regular assignment)
- Only valid as a standalone statement or in augmented assignment forms like +=.
- := (walrus operator)
- Valid inside while and if conditions, comprehension filters, and any expression context — but not as a standalone top-level statement.
- = (regular assignment)
- Available in every version of Python.
- := (walrus operator)
- Requires Python 3.8 or later. Using it on an earlier interpreter raises a SyntaxError.
Under the Hood: What CPython Actually Does
Most tutorials stop at usage patterns. Understanding what CPython does internally when it encounters := is rarer, and it resolves a question that often comes up: does the walrus operator carry any performance cost?
When CPython compiles a walrus expression, the exact bytecode depends on your Python version. In Python 3.8 through 3.10, the compiler emits a DUP_TOP opcode followed by STORE_FAST (or STORE_NAME at module scope). DUP_TOP duplicates the value at the top of the evaluation stack, increments its reference count, and pushes the copy back. STORE_FAST then binds that copy to the named variable. Starting with Python 3.11, DUP_TOP was removed from CPython's opcode set and replaced by the more general COPY instruction (which takes a stack position argument). The walrus operator uses COPY 1 followed by STORE_FAST in Python 3.11 and later. The semantic outcome is identical in all versions: the assignment and the expression result refer to the same Python object — not two separate objects.
In practice, benchmarks show the overhead is negligible. The extra stack instruction adds a reference-count increment, but across millions of iterations the delta is in the microseconds-per-call range — well below any I/O or function-call cost that typically accompanies the patterns where := is most useful.
You can verify this with the dis module. Run import dis; dis.dis(lambda: (n := len([1,2,3])) > 2) in your interpreter. On Python 3.8–3.10, look for DUP_TOP before the store operation. On Python 3.11 and later, you will see COPY 1 in its place — same purpose, different opcode name.
The type annotation limitation
One constraint that rarely appears in tutorials: you cannot combine an inline type annotation with a walrus expression in the same statement. If you need a type annotation, you must split the declaration:
from typing import Optional
# This raises a SyntaxError — you cannot annotate a walrus target inline
# value: Optional[int] = None
# while value := some_func(): ... # SyntaxError
# Correct pattern: declare with annotation first, then use walrus
value: Optional[int] = None
while value := some_func():
process(value)
This matters in typed codebases using mypy or Pyright. The walrus operator fully supports type inference on its own — the type of the bound variable is inferred from the right-hand side — but it does not support the annotation syntax in the same expression. Plan accordingly when annotating functions or modules that make heavy use of :=.
The code below is meant to read non-empty lines from a file into a list. There is exactly one bug. Click the line you think is wrong, then hit check.
while line := f.readline().strip(): or the filter should happen at the append step. The current code appends every line — including lines that are whitespace-only — because readline() returns "\n" for blank lines, which is truthy. The walrus condition passes these through. To exclude blank lines, strip before the truthiness test: while line := f.readline().strip():. That way blank lines become empty strings, which are falsy, and the loop skips them.
The comprehension target-name restriction
PEP 572 includes one non-obvious restriction on comprehension use: the walrus target name cannot be the same as the comprehension's own iteration variable. This is enforced at compile time and raises a SyntaxError.
data = [1, 2, 3, 4]
# SyntaxError — 'x' is both the loop variable and the walrus target
# bad = [x for x in data if (x := x * 2) > 4]
# Correct — use a different name for the walrus target
good = [y for x in data if (y := x * 2) > 4]
print(good) # [6, 8]
print(y) # 8 — leaks to enclosing scope (intentional by design)
The code below is meant to filter a list and then use the last processed value. There is exactly one bug — a misunderstanding about walrus scoping. Click the line you think is wrong, then hit check.
y to hold the highest filtered value (90), but := assigns on every iteration — including iterations that fail the filter. y ends up as 20 (the last x * 10 computed, from x = 2), regardless of filtering. If you need the largest passing value, use max(big) directly. The walrus operator is not a running maximum accumulator — it is a per-iteration binding that always reflects the most recent assignment.
The Operator That Changed Python's Governance
The walrus operator has a history that goes well beyond syntax. PEP 572 is the only Python Enhancement Proposal that triggered the resignation of Guido van Rossum as BDFL — Benevolent Dictator for Life — a role he had held since Python's creation in 1991.
Chris Angelico submitted the original proposal in February 2018. What followed was one of the most contentious discussions in the python-dev and python-ideas mailing lists in the language's history. The arguments against the operator were philosophically grounded: it appeared to violate "There should be one — and preferably only one — obvious way to do it" from the Zen of Python, and critics argued that doing two things (assigning and returning a value) in a single expression made intent harder to read at a glance.
Guido accepted the PEP in July 2018. Six days later, he announced his permanent withdrawal from the BDFL role, writing to the python-committers list that he no longer felt he had the trust of the core development team. The Python community then organized, proposed seven different governance models, and in December 2018 adopted the Steering Council model — a five-person elected body that now governs the language. Guido himself served on the first steering council.
The irony the community eventually came to appreciate: the operator that caused the most drama has also proven, in practice, to be one of the less controversial features to actually use. Its footprint in production code is modest and targeted, exactly as the PEP's authors intended.
The Python 3.8 release documentation advises using := only where it genuinely reduces complexity and aids readability — not as a default shorthand for any two-line pattern. (What's New In Python 3.8, Python Software Foundation)
Walrus Operator Limitations and Pitfalls
Knowing what := cannot do is just as useful as knowing what it can do. A few restrictions trip up developers who are new to it.
You cannot use it as a standalone statement. Writing x := 5 on a line by itself is a syntax error. The operator must always appear inside a larger expression. Wrap it in parentheses if you genuinely need a standalone form: (x := 5) is valid, though it offers nothing over a plain x = 5.
Variables bound in comprehensions leak to the enclosing scope. This is intentional by design but can be surprising. If you write [y for x in data if (y := transform(x))], the variable y holds the last value assigned to it after the comprehension finishes. This is different from regular comprehension loop variables, which are local to the comprehension.
Before you look at the code below, predict: after results = [y for x in [1, 2, 3, 4] if (y := x * 2) > 4] runs, what does print(y) output? Think it through, then read the code and comment to check yourself. This is one of the most commonly misunderstood behaviors of :=.
data = [1, 2, 3, 4]
results = [y for x in data if (y := x * 2) > 4]
print(y) # 8 — y is accessible here (last value assigned)
It cannot appear unparenthesized inside f-string expression parts. Because the : character already signals a format specification in f-strings (e.g., f"{value:.2f}"), the parser cannot unambiguously handle a bare := inside curly braces. The unparenthesized form raises a SyntaxError. Wrapping the walrus expression in parentheses inside an f-string is technically valid — f"{(x := compute())}" — but PEP 572 explicitly discourages it. Keep walrus expressions out of f-strings in practice.
Do not use := to replace every two-line assignment you see. The operator reduces visual noise when it eliminates a genuine redundancy. Using it just to condense code without a clear benefit tends to hurt readability rather than help it.
The function below reads integers from user input until the user types "done". There is one bug. Click the line you think is wrong, then hit check.
values.append(entry) to values.append(int(entry)). The walrus operator captures the raw string from input(). Appending the string directly and then calling sum() on a list of strings raises a TypeError. The string must be converted to an integer before it is stored.
How to Use the Walrus Operator in Python
Applying := effectively comes down to recognizing the right pattern and replacing it with a single expression. These three steps cover the most common cases.
-
Identify where a value is computed and tested in the same statement
Look for code that assigns the result of a function or expression to a variable on one line, then immediately tests or uses that variable on the next. These two-line pairs are the clearest candidates for the walrus operator. Common signals are a pre-loop read before a
whilecondition, a separate assignment before aniftest, or a function called twice in the same comprehension. -
Replace the two-step pattern with a single := expression
Move the assignment directly into the condition or filter using
name := expression. For awhileloop, this goes inside the condition:while chunk := f.read(1024):. For a comprehension, it goes in theifclause:[y for x in data if (y := transform(x)) is not None]. For a conditional, it replaces the pre-assignment:if m := re.search(pattern, line):. -
Wrap the := expression in parentheses when needed for clarity
Parentheses are required in some contexts to avoid a
SyntaxErrorand are always helpful when the precedence might be unclear to a reader. Inside a comprehension filter the parentheses are mandatory. Inside a barewhileorifcondition they are optional but recommended by style guides when the walrus expression is not the entire condition. When in doubt, add them.
Python Learning Summary Points
- The walrus operator (
:=) is a named expression that assigns a value to a variable and returns that value in the same expression, introduced in Python 3.8 via PEP 572. - Beyond the well-known while-loop and regex patterns,
:=provides the most leverage in lazy generator pipelines, multi-condition chains where each condition depends on the previous result,any()/all()short-circuit captures, database cursor streaming without materializing a full result set, stateful token parsing without a class, and bounded retry loops with exponential backoff. - Variables bound by
:=inside a comprehension are scoped to the enclosing function or module, not to the comprehension itself — an important distinction from regular comprehension loop variables that are always local. - Under CPython,
:=compiles toDUP_TOP+STORE_FASTin Python 3.8–3.10, and toCOPY 1+STORE_FASTin Python 3.11 and later (whereDUP_TOPwas removed from the opcode set). The overhead is a single stack operation and a reference-count increment — negligible in any real-world workload. - You cannot combine an inline type annotation with a walrus target in the same expression. Declare the annotated variable first, then use
:=in the loop or condition. - The walrus target name inside a comprehension cannot match the comprehension's own loop variable. Use a distinct name for the walrus binding.
- PEP 572 is the only Python proposal that led directly to Guido van Rossum stepping down as BDFL. The controversy produced Python's current Steering Council governance model as a lasting structural outcome.
The walrus operator is not a replacement for readable code — it is a tool for removing genuine redundancy. When the two-line pattern it replaces would make a reviewer read the same computation twice, := earns its place. Use it with that criterion in mind and it will serve you well.
Frequently Asked Questions
The walrus operator (:=) is a named expression operator introduced in Python 3.8. It assigns a value to a variable as part of an expression, so the variable and the value can both be used in the same statement without needing a separate assignment line.
The nickname comes from the visual resemblance of := to the eyes and tusks of a walrus when rotated. The official name from PEP 572 is "assignment expression" or "named expression."
The walrus operator was introduced in Python 3.8, released in October 2019, through PEP 572.
The standard = operator is a statement that assigns a value but returns nothing and cannot be embedded inside an expression. The := operator is an expression that assigns a value and returns it, allowing the result to be used immediately within a larger expression such as a while condition or list comprehension filter.
Use := when you need to assign a value and test or use it in the same expression — most commonly inside while loop conditions, list comprehension filters, and if statements where the computed value would otherwise need to be calculated twice or stored in a separate line before the expression.
Yes. The := operator can be used inside the condition clause of a list comprehension to capture an intermediate result that is then reused in the output expression, avoiding redundant computation. Parentheses around the walrus expression are required inside a comprehension.
Avoid := when a regular assignment followed by a conditional would be equally clear. Using it in deeply nested expressions can harm readability. The operator is valuable when it removes a real redundancy — not simply to save a line of code.
No. The := operator cannot be used as a standalone top-level statement without wrapping parentheses. Inside f-strings, the bare := form causes a SyntaxError because the colon already signals a format specification in that context; wrapping the walrus expression in parentheses inside an f-string is technically valid but strongly discouraged by PEP 572. The operator is also not valid in augmented assignment targets, and using it as a default argument value in function definitions without parentheses raises a SyntaxError.
Not meaningfully. In Python 3.8 through 3.10, CPython compiles := to a DUP_TOP opcode followed by STORE_FAST, which duplicates the value at the top of the evaluation stack and binds it to the named variable. Starting with Python 3.11, DUP_TOP was removed from the opcode set and replaced by the more general COPY instruction; := now uses COPY 1 followed by STORE_FAST. In all versions, the overhead is a single stack operation and a reference-count increment — benchmarks show a delta in the microseconds-per-call range across millions of iterations. In any real pattern where you would reach for :=, the I/O or function call being captured will dominate by orders of magnitude.
Not inline. You cannot combine a type annotation with := in the same statement. Declare the annotated variable on its own line first — for example, value: Optional[int] = None — and then use := in the loop or conditional. The walrus operator does support type inference on its own; the bound variable's type is inferred from the right-hand side and recognized correctly by mypy and Pyright.
Yes, with the same rules that apply to list comprehensions and generator expressions. You can use := in the filter clause of a dict or set comprehension, and the bound variable leaks to the enclosing scope. The walrus target name cannot match the comprehension's loop variable. As with list comprehensions, parentheses around the walrus expression are required inside the filter clause.
data = [1, 4, 9, 16, 25]
# Dict comprehension: capture sqrt, include only values whose root is whole
perfect = {x: root for x in data if (root := x ** 0.5) == int(root)}
print(perfect) # {1: 1.0, 4: 2.0, 9: 3.0, 16: 4.0, 25: 5.0}
Yes. Because assert takes an expression, := is valid inside it. This pattern lets you capture a computed value and assert a condition about it in one line, then reference the captured value in the assertion message:
import re
line = "Error: disk quota exceeded"
assert (m := re.search(r"Error: (.+)", line)), f"Expected error pattern, got: {line!r}"
print(m.group(1)) # disk quota exceeded
The binding persists after the assert passes, so m is available for further use. Be aware that assert statements are stripped when Python runs with the -O (optimize) flag, so do not rely on a walrus binding inside an assert for program logic that must always execute.
Both patterns have legitimate uses. The while True: ... if not x: break form is preferable when the exit condition depends on something that happens inside the loop body rather than at the top. The walrus condition — while chunk := f.read(1024): — is preferable when the sentinel value is determined solely by the expression in the condition and the entire loop body processes that value. The walrus form expresses intent more directly in the second case: the loop continues as long as the expression produces a truthy result, and the body always receives a valid value. Mixing both idioms in the same loop is a code smell worth avoiding.
They can be, but doing so almost always harms readability more than it helps. Nesting := expressions — for example, if (a := f()) and (b := g(a)) and (c := h(b)): — is syntactically valid and sometimes the clearest way to express a dependent chain of operations where each step feeds the next. Beyond two levels, however, the intent becomes difficult to follow at a glance and a conventional multi-line approach is usually cleaner. The official guidance from PEP 572 is to limit walrus use to cases that reduce complexity; nested walrus expressions frequently violate that principle.
Yes, and this is one of the most practical everyday uses. When a function returns either a meaningful value or None, you can capture the result and test it in the same line:
def get_user(user_id):
# Returns a dict or None
return db.find(user_id)
if user := get_user(42):
print(user["name"])
else:
print("User not found")
One important caveat: this pattern relies on truthiness, not identity. If the function can return a legitimate falsy value — an empty string, zero, an empty list — the walrus condition will treat it as a miss and take the else branch. In those cases, test explicitly: if (result := get_data()) is not None:.
Not inside pattern clauses. Python's structural pattern matching (introduced in Python 3.10 via PEP 634) uses its own capture variable syntax — a bare lowercase name in a pattern position already acts as a capture binding. Using := inside a case pattern raises a SyntaxError. The walrus operator can be used in the subject expression that the match statement tests, or inside a guard (case ... if condition:), but not inside the pattern itself. If you need to capture part of a matched structure, use the pattern binding syntax that match provides natively.