Walrus Operator (:=) in Python

The python walrus operator is one of the more debated additions to modern Python — genuinely useful in the right spots, genuinely overused in the wrong ones. This article covers what it actually does, where it earns its place, and the scoping quirks that have tripped up even experienced developers.

What Is the Walrus Operator?

The walrus operator, written :=, was introduced in Python 3.8 through PEP 572. Officially, it's called an assignment expression — and that name is the whole point. A regular = assignment in Python is a statement; it doesn't produce a value you can use anywhere else. := is different: it assigns a value to a variable and returns that value, all within a single expression.

The "walrus" nickname comes from the symbol's shape — := is said to resemble a walrus's eyes and tusks turned sideways. It's an informal name, but it's the one that stuck, and you'll see walrus operator used interchangeably with "assignment expression" throughout Python discussions.

The core idea
# Regular assignment — a statement, can't be used inside another expression
n = 10

# Assignment expression — assigns AND returns the value, usable inline
if (n := 10) > 5:
    print(n)

That distinction — statement versus expression — is what unlocks everything else in this article.

Basic Syntax and How It Differs from =

The classic motivating example
data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]

# Traditional two-line version
n = len(data)
if n > 10:
    print(f"Data has {n} items")
# Same logic, using the walrus operator
if (n := len(data)) > 10:
    print(f"Data has {n} items")

Both do exactly the same thing. The walrus version avoids declaring n on its own separate line, while still giving you a name you can reuse inside the if block.

Why plain = can't do this

Python deliberately keeps = as a statement, not an expression — this is intentional language design, not an oversight. It's why something like if n = len(data): has always been a syntax error in Python, even though the equivalent is legal in languages like C or JavaScript. The walrus operator exists specifically to provide a safe, explicit way to do this kind of inline assignment, without reopening the door to the classic =-versus-== typo bug that plain assignment-as-expression invites in those other languages.

Parentheses are usually required

In most contexts, := needs to be wrapped in parentheses:

if (n := len(data)) > 10:
    print(n)
Precedence matters

This is the detail most likely to catch you off guard: := binds lower than most operators, including comparison operators. That means writing it without parentheses can silently produce something very different from what you intended:

if n := 5 > 4:
    print(n)

This doesn't assign 5 to n and then check if it's greater than 4. Because := has lower precedence than >, Python evaluates 5 > 4 first (giving True), and that's what gets assigned to n. The output is True, not 5 — probably not what you expected from reading the line. Always wrap the walrus assignment itself in parentheses around exactly the value you mean to capture: (n := 5) > 4.

Practical Use Cases

Avoiding repeated or expensive function calls

The most common motivation for using := is avoiding calling the same function twice — once to compute a value, and again inside a condition or comprehension that uses it:

# Without walrus — complex_calculation() runs, and the result needs a separate line
results = []
for x in data:
    value = complex_calculation(x)
    if value > threshold:
        results.append(value)
# With walrus, inside a list comprehension
results = [value for x in data if (value := complex_calculation(x)) > threshold]

The second version computes complex_calculation(x) exactly once per item, reusing it for both the filter condition and the final collected value — instead of calling it twice or restructuring the whole thing into a full loop.

while loops reading until a sentinel

This is arguably the walrus operator's best-fit use case: reading from a file, socket, or user input until you hit some ending condition.

# Reading a file line by line until it's exhausted
with open("data.txt") as file:
    while (line := file.readline()):
        print(line.strip())

Without the walrus operator, this pattern traditionally required either a slightly awkward while True: loop with a manual break, or reading the first line before the loop and again at the end of each iteration — both noticeably more repetitive than the version above.

Regex matching in one line

Regular expression matching often involves capturing a match object and immediately checking whether it succeeded — a natural fit for :=:

import re

pattern = re.compile(r"\d+")
text = "There are 42 apples"

if match := pattern.search(text):
    print(f"Found: {match.group()}")

This avoids a separate match = pattern.search(text) line before the if, without losing access to the match object inside the block.

Common Pitfalls and Gotchas

Variable scope

Here's a subtlety worth understanding well: a variable assigned with := inside a comprehension binds to the enclosing scope, not to the comprehension's own internal scope, by design. This is different from the comprehension's own loop variable, which stays contained.

def check_values(data, threshold):
    results = [y for x in data if (y := x * 2) > threshold]
    print(y)   # this works — y leaked out of the comprehension into the function scope
    return results

This is intentional behavior per PEP 572, not a bug — but it did have a rough edge in early releases: in Python 3.8 and 3.9, this leaking behavior could behave inconsistently in certain scopes, such as at class-body or module level. That inconsistency was cleaned up by Python 3.10, and the behavior has been stable and well-defined since. If you're working in a codebase that still needs to support 3.8 or 3.9, it's worth testing this specific pattern carefully rather than assuming it behaves identically everywhere.

Where it's disallowed entirely

A few contexts don't permit := at all:

  • Plain top-level assignment — you can't write n := 5 as a standalone statement in place of n = 5. The walrus form must appear as part of a larger expression.

  • Inside a lambda function's body — walrus assignment isn't permitted there.

  • As the context manager expression in a with statementwith (f := open("file.txt")): would bind f to the file object itself returned by the expression, not to what __enter__() returns, which is usually not what you actually want from a with statement. This is a subtle enough trap that it's worth avoiding the pattern entirely rather than trying to reason through it each time.

The readability risk

Because := lets you cram an assignment into what used to be a simple condition or comprehension, it's easy to overuse. Multiple walrus operators nested in a single dense expression, or a walrus assignment buried inside an already-complicated comprehension, can genuinely make code harder to follow rather than easier — the opposite of the readability goal PEP 572 was designed around.

Best Practices: When to Use It (and When Not To)

Good fits
  • while loops that read incrementally — files, sockets, streamed input — until some ending condition.

  • Guard clauses where you need both the result and a check on it: if (result := risky_call()) is not None:.

  • Comprehensions with an expensive filter computation that would otherwise need to run twice.

When to skip it

If a plain, two-line traditional assignment is just as clear, use that instead. The walrus operator wasn't designed to compress every possible assignment into an expression — it exists specifically to eliminate genuine redundancy, like a duplicated function call. If there's no redundancy to eliminate, reaching for := mostly just adds unfamiliar syntax without a real benefit.

Guidance for team codebases

If you're working with other developers, treat := as a tool for clarity, not an opportunity to show off compact syntax. A good rule of thumb: if you have to pause and mentally unpack what a line is doing before you can explain it to someone else, it's probably not the right spot for a walrus operator — even if it technically works.