Walrus Operator (:=)

Concepts covered: Variables

name := expression evaluates the expression, binds the result to name, and hands the same value to whatever surrounds it. Plain = is a statement and cannot sit inside a larger expression. :=, nicknamed the walrus operator for its eyes and tusks, is an expression. It arrived in Python 3.8, so any 3.8 or later interpreter accepts it.

print((n := 7) * 2)
print(n)

Output:

14
7

Parentheses decide what gets bound

:= binds more loosely than every other operator, so the comparisons and even and and or are evaluated before it. Inside the parentheses, count := 3 > 2 binds count to the result of 3 > 2, which is the bool True, and the 3 itself is never stored. Wrap the walrus around exactly the value you want to keep.

ok = (count := 3) > 2
print(ok, count)
ok = (count := 3 > 2)
print(ok, count)

Output:

True 3
True True

Compute once, test, then reuse

The walrus pays off where an expression needs a value twice: once to test it and once to report it. Paired with a short-circuit guard, it computes the average row width only after the zero check passes, and keeps the result for the next line.

total_bytes = 7340032
row_count = 2048
too_wide = row_count != 0 and (avg := total_bytes / row_count) > 1024
print(too_wide, avg)

Output:

True 3584.0

The binding happens only if Python reaches the walrus. Set row_count to 0 and and stops at its left operand, so the name avg is never created and reading it raises NameError.

total_bytes = 7340032
row_count = 0
too_wide = row_count != 0 and (avg := total_bytes / row_count) > 1024
print(too_wide)
try:
    print(avg)
except NameError as err:
    print("NameError:", err)

Output:

False
NameError: name 'avg' is not defined

A guard over a stream of readings

The animation pulls 3 readings from a generator and checks each on a single line. The walrus captures the reading, the identity test lets the real zero through as a reading, and the comparison runs only when the feed produced a number.

def readings():
    yield 12.5
    yield 0.0
    yield None


stream = readings()
ok1 = (r1 := next(stream)) is not None and r1 > 10
ok2 = (r2 := next(stream)) is not None and r2 > 10
ok3 = (r3 := next(stream)) is not None and r3 > 10
print(ok1, ok2, ok3)
print(r1, r2, r3)
print(r1 and r1 > 10, r2 and r2 > 10)
True False False
12.5 0.0 None
True 0.0

The last print shows why the identity test matters. With truthiness in its place, the reading of 0.0 short-circuits to the float 0.0, and a report treating that value as a check result would mix a measurement into a yes or no column.

Do
  • Wrap every walrus in parentheses around the exact value to keep
  • Bind the value where it is first needed, then reuse the name
  • Read a name bound inside and or or only on the path that bound it
Avoid
  • Pack several walruses into a line nobody can scan
  • Use := where an ordinary = statement on its own line reads fine
  • Read a walrus name after a short-circuit that may have skipped it

What does this print?

row_count = 4
ok = (wide := 5000 / row_count > 1000) and row_count > 2
print(ok, wide)

Capstone: one event, every operator

The last 2 problems put the whole lesson into a single pipeline step. It decodes a message event's ID and tests a permission bit before guarding a division; after that it reads present values and compares float results.

A pipeline step like this one usually breaks through a small edit, and most such edits are one of 3 slips: a guard moved to the right of the division it protects, so the division runs first; a revoke written with |, which can only turn bits on, where & ~ was needed; and a float result checked with == after it took a different arithmetic path from its target. The next program contains one of each.

About this lesson

This section is part of the Python Expressions: Advanced lesson on DataDriven, the home of data engineering. Lessons cover SQL, Python, Spark, data modeling and pipeline architecture, and each section pairs its explanation with worked examples and practice you run as you go.