skip to content

What does Python's walrus operator `:=` do, and how does it differ from `=`?

level: juniorimportance: must knowfreq 65%

answer

  1. Assign and use in one step
  2. Statements yield nothing; this yields something
  3. Named after eyes and tusks
  4. PEP 572, arrived in Python 3.8

basics

~20 s

:= is an assignment expression: it binds a name and also evaluates to the assigned value, so it can be used inside a condition or a call. Plain = is a statement, which produces no value at all.

solid answer

~50 s

Python separates statements from expressions, and `x = 5` is a statement: it rebinds the name and yields nothing, so it cannot appear where a value is expected. The walrus operator, added by PEP 572 in Python 3.8, gives assignment an expression form: `(n := len(s))` binds `n` and the whole parenthesised expression evaluates to that same length, so you can test it in the same breath. The classic payoffs are capture-and-test — `if (m := re.search(pattern, line)):` then use `m` in the body — and read-until-empty loops, both of which otherwise force you to either compute a value twice or write a priming read before the loop. The target of `:=` must be a bare name: attributes, subscripts and tuple unpacking are rejected, and a bare `x := 5` on its own line is a `SyntaxError`.

code

pycon · 7 lines
pycon
>>> n = 5
>>> print(n := 7)
7
>>> n
7
>>> (n := 9) + 1
10

go deeper

for a junior

Be ready to state in one sentence that := both binds a name and evaluates to the value, while = is a statement with no value, and to show the if (m := ...) capture-and-test shape.

for a middle

Explain the mechanics: the target must be a bare identifier, the binding lands in the ordinary enclosing scope, and a bare x := 5 line is a SyntaxError by design rather than by accident.

for a senior

Show judgement about where it belongs in production code — removing a duplicated expensive call or a priming read is a win; two walruses in one condition is a review comment.

for a principal

Own the style position for the codebase: whether := is encouraged, and what your minimum supported interpreter is, since anything older than Python 3.8 cannot parse it at all.

### Statements have no value; expressions do Python draws a hard line between statements and expressions. An expression evaluates to an object: `len(s)`, `a + b`, `f(x)`. A statement performs an action and yields nothing you can capture: `import`, `return`, `for`, and — crucially — ordinary assignment. In C you can write `while ((c = getchar()) != EOF)` because `=` there is an operator that produces a value. Python deliberately made `=` a statement, largely to stop `if (x = 1)` typos from silently passing where `if (x == 1)` was meant. That decision cost something. Any time you wanted to *use* a value and *test* it, you had to write the value twice, or bind it on a line before and lose the tight coupling between the test and its subject. ### What `:=` actually is PEP 572 (Python 3.8) added the **assignment expression**, spelled `NAME := expression` and nicknamed the walrus operator for its resemblance to eyes and tusks. It does two things in one evaluation: 1. it evaluates the right-hand expression and binds the result to the name, exactly as `=` would; 2. the assignment expression itself evaluates to that same object. So `(n := len("hello"))` is an expression worth `5` that also leaves `n` bound to `5`. It is the same binding machinery — the name lands in the enclosing function, class or module scope, exactly where a normal `=` on the same line would put it. Nothing is copied; the name is bound to the same object the right-hand side produced. ### The three idioms it exists for **Capture and test.** Instead of calling a matcher, storing the result, then testing it on the next line, you fold the two together: ```python import re if m := re.fullmatch(r"(\w+)_(\d+)", name): print(m[1], m[2]) ``` The body only runs when the match succeeded, and `m` is right there to use. **Read until the source is exhausted.** Without `:=`, you either duplicate the read or write `while True:` with a `break`. With it, the loop condition carries the read. **Avoid computing an expensive value twice.** `if (total := expensive(x)) > limit: report(total)` calls `expensive` once, where the naive version either calls it twice or needs a preceding line. ### The rules that trip people up The **target must be a single plain identifier**. `obj.attr := 1`, `d["k"] := 1` and `(a, b) := pair` are all `SyntaxError`; if you need those, use a normal assignment statement. There is also no augmented form — `x += 1` has no expression counterpart. An assignment expression is **not allowed to stand alone as a statement**: `x := 5` on its own line is a `SyntaxError`, precisely so that nobody writes it where `=` was intended. Parenthesise it, or just use `=`. The same rule bites on the right-hand side of an assignment (`y = (x := 0)` is fine, `y = x := 0` is not) and on the value of a keyword argument in a call (`f(mode=(m := "RGB"))` needs the parentheses; a positional `f(m := "RGB")` does not). Because `:=` binds more loosely than every other operator, `if n := len(s) > 10:` does not do what it looks like — it binds `n` to a boolean. That is a silent wrong-value bug rather than a syntax error, which is why the parenthesised form is the one you see in every example. ### Where you will actually see it Two positions account for almost every real use. In a `while` condition it carries the call that both advances the loop and decides when to stop. In an `if` condition it captures the thing the test is about — a match object, a lookup result, a parsed value — so the body can use it without recomputing. Outside conditions the operator is legal but rarely worth it: at the top of a statement you already have `=`, which is shorter and puts the name where a reader's eye expects to find it. ### When it earns its place The operator is a readability tool, and it can cut both ways. It pays when it removes a repeated computation, a priming read, or a temporary variable whose only job was to be tested on the next line. It stops paying when several walruses share one condition, when the bound name is used far away from where it was bound, or when the expression grows long enough that a reader has to hunt for the binding. The style rule most teams settle on is simple: one assignment expression per condition, used immediately, or don't use it. Everything above is true on Python 3.14; the semantics have not changed since 3.8, and the only compatibility question is whether your minimum supported interpreter is 3.8 or newer.

  • Can you write `count := 5` as a standalone line?
    No — an unparenthesized assignment expression at the top level of an expression statement is a `SyntaxError`. PEP 572 forbade it deliberately, so that a mistyped `:=` can never masquerade as an ordinary assignment. Write `count = 5`, or parenthesise it as `(count := 5)` if you genuinely want the expression form, though on its own line that buys you nothing.
  • What can appear on the left of `:=`?
    Only a single plain identifier. Attribute targets like `obj.attr := 1`, subscripts like `d[key] := 1`, and unpacking targets like `(a, b) := pair` are all rejected at compile time with a `SyntaxError`. There is no augmented form either — nothing corresponds to `x += 1`. When you need any of those, use a normal assignment statement on its own line.
  • Does `:=` bind the name any differently from `=`?
    No. It uses the same binding rules: inside a function the name is a local, at module level it is a global, and `global` or `nonlocal` declarations apply to it exactly as they would to a normal assignment. The only difference is that the expression form also produces the assigned object as its value, so it can sit inside a condition or a call.

Ordinary = is like posting a letter: the delivery happens, but you are not handed anything back. := is like a shop receipt — the transaction completes and you walk away holding proof of the amount, ready to show it at the door.

saying these in an interview costs you the question

  • Calls `:=` a synonym for `=` with nicer syntax
  • Believes plain `x = 5` evaluates to 5 in Python
  • Says `:=` can assign to `obj.attr` or `d[key]`
  • Writes `x := 5` as a standalone statement line
  • Thinks `:=` creates a fresh scope for the name
  • Claims `:=` copies the value rather than binding it

context