|$ curl https://forge-ai.dev/api/markdown?path=docs/python/walrus
$cat docs/python-—-walrus-operator-(:=).md
updated Last week·16 min read·published
Python — Walrus Operator (:=)
Introduction
The walrus operator := (introduced in Python 3.8, PEP 572) assigns a value to a variable as part of an expression. Before walrus, you had to assign a value on one line and use it on the next. Now you can do both in a single expression.
The name "walrus" comes from the operator's resemblance to a walrus with tusks: := looks like (: rotated 90 degrees.
Syntax Basics
syntax.py
Python
| 1 | # Before walrus — two separate statements |
| 2 | data = get_data() |
| 3 | if data: |
| 4 | process(data) |
| 5 | |
| 6 | # With walrus — assign and check in one line |
| 7 | if (data := get_data()): |
| 8 | process(data) |
| 9 | |
| 10 | # The parenthesization matters |
| 11 | # (x := value) — walrus expression |
| 12 | # x = value — regular assignment (not an expression) |
| 13 | |
| 14 | # Basic syntax |
| 15 | y = (n := 10) # y = 10, n = 10 |
| 16 | print(y, n) # 10 10 |
| 17 | |
| 18 | # Works inside any expression |
| 19 | a = (b := 5) + 3 # a = 8, b = 5 |
| 20 | print(a, b) # 8 5 |
| 21 | |
| 22 | # Walrus returns the assigned value |
| 23 | print((x := "hello")) # prints "hello" |
| 24 | |
| 25 | # Without walrus, you'd write: |
| 26 | temp = compute_expensive() |
| 27 | result = temp * 2 |
| 28 | |
| 29 | # With walrus: |
| 30 | result = (temp := compute_expensive()) * 2 |
ℹ
info
Always use parentheses around walrus expressions when combining with other operators. Without parens, operator precedence can cause unexpected results.
While Loops
The most common walrus use case is reading data in a loop condition. Without walrus, you need a sentinel value and a duplicated condition check.
while_loops.py
Python
| 1 | # Without walrus — verbose sentinel pattern |
| 2 | line = input("Enter text: ") |
| 3 | while line != "quit": |
| 4 | print(f"You said: {line}") |
| 5 | line = input("Enter text: ") |
| 6 | |
| 7 | # With walrus — cleaner, no duplication |
| 8 | while (line := input("Enter text: ")) != "quit": |
| 9 | print(f"You said: {line}") |
| 10 | |
| 11 | # Reading from a file line by line |
| 12 | with open("data.txt") as f: |
| 13 | while (line := f.readline()): |
| 14 | process(line.strip()) |
| 15 | |
| 16 | # Reading chunks from a stream |
| 17 | buffer = b"" |
| 18 | while (chunk := stream.read(1024)): |
| 19 | buffer += chunk |
| 20 | if len(buffer) > 8192: |
| 21 | flush(buffer) |
| 22 | buffer = b"" |
| 23 | |
| 24 | # Processing a queue until empty |
| 25 | from collections import deque |
| 26 | queue = deque([1, 2, 3, 4, 5]) |
| 27 | |
| 28 | while (item := queue.popleft()): |
| 29 | print(f"Processing {item}") |
| 30 | |
| 31 | # Fibonacci with walrus |
| 32 | a, b = 0, 1 |
| 33 | while (a := a + b) < 100: |
| 34 | print(a, end=" ") |
| 35 | # Output: 1 2 3 5 8 13 21 34 55 89 |
If Conditions
if_conditions.py
Python
| 1 | # Cache expensive computation in an if-check |
| 2 | import re |
| 3 | |
| 4 | # Without walrus |
| 5 | match = re.search(r"\d{4}-\d{2}-\d{2}", text) |
| 6 | if match: |
| 7 | date_str = match.group() |
| 8 | print(f"Found date: {date_str}") |
| 9 | |
| 10 | # With walrus |
| 11 | if match := re.search(r"\d{4}-\d{2}-\d{2}", text): |
| 12 | print(f"Found date: {match.group()}") |
| 13 | |
| 14 | # Validate and process in one step |
| 15 | user_input = " " |
| 16 | if (cleaned := user_input.strip()): |
| 17 | process(cleaned) |
| 18 | else: |
| 19 | print("Empty input") |
| 20 | |
| 21 | # Dictionary processing |
| 22 | data = {"name": "Alice", "age": 30, "city": "NYC"} |
| 23 | |
| 24 | # Without walrus |
| 25 | if "age" in data: |
| 26 | age = data["age"] |
| 27 | print(f"Age: {age}") |
| 28 | |
| 29 | # With walrus |
| 30 | if (age := data.get("age")) is not None: |
| 31 | print(f"Age: {age}") |
| 32 | |
| 33 | # Chained checks |
| 34 | config = {"debug": True, "log_level": "INFO"} |
| 35 | |
| 36 | if config.get("debug") and (level := config.get("log_level")): |
| 37 | print(f"Debug mode, level: {level}") |
| 38 | |
| 39 | # Short-circuit with walrus |
| 40 | def expensive_check(x): |
| 41 | print(f"Checking {x}...") |
| 42 | return x > 0 |
| 43 | |
| 44 | # Only runs expensive_check once — result reused in else |
| 45 | if (result := expensive_check(42)): |
| 46 | print(f"Passed: {result}") |
| 47 | else: |
| 48 | print(f"Failed: {result}") # uses cached result, no re-computation |
List Comprehensions
comprehensions.py
Python
| 1 | # Walrus inside comprehensions — assign and filter in one pass |
| 2 | |
| 3 | # Without walrus — calls is_valid() twice |
| 4 | items = [1, 2, 3, 4, 5, 6, 7, 8] |
| 5 | filtered = [is_valid(x) for x in items if is_valid(x)] |
| 6 | |
| 7 | # With walrus — calls is_valid() once |
| 8 | filtered = [y for x in items if (y := is_valid(x))] |
| 9 | |
| 10 | # Processing strings with regex in comprehension |
| 11 | import re |
| 12 | texts = ["hello world", "foo123bar", "no numbers", "abc42def"] |
| 13 | |
| 14 | # Extract numbers from texts |
| 15 | numbers = [ |
| 16 | int(m.group()) |
| 17 | for text in texts |
| 18 | if (m := re.search(r"\d+", text)) |
| 19 | ] |
| 20 | print(numbers) # [123, 42] |
| 21 | |
| 22 | # Square root with threshold |
| 23 | import math |
| 24 | values = [1, 4, 9, 16, 25, 30, 49, 50] |
| 25 | |
| 26 | # Only keep values whose square root is an integer |
| 27 | perfect = [ |
| 28 | root |
| 29 | for x in values |
| 30 | if (root := math.isqrt(x)) ** 2 == x |
| 31 | ] |
| 32 | print(perfect) # [1, 2, 3, 4, 5, 7] |
| 33 | |
| 34 | # Nested comprehension with walrus |
| 35 | matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] |
| 36 | |
| 37 | # Find all elements greater than their row average |
| 38 | above_avg = [ |
| 39 | (row_idx, val) |
| 40 | for row_idx, row in enumerate(matrix) |
| 41 | for val in row |
| 42 | if val > (avg := sum(row) / len(row)) |
| 43 | ] |
| 44 | |
| 45 | # Dict comprehension with walrus |
| 46 | raw = {"a": " 1 ", "b": " 2 ", "c": " 3 "} |
| 47 | cleaned = { |
| 48 | k: (v := v.strip()) |
| 49 | for k, v in raw.items() |
| 50 | if v.strip() # filter out empty after strip |
| 51 | } |
| 52 | |
| 53 | # Set comprehension |
| 54 | words = ["hello", "world", "python", "hi", "code"] |
| 55 | short = { |
| 56 | word.upper() |
| 57 | for word in words |
| 58 | if len(word) <= (threshold := 4) |
| 59 | } |
| 60 | print(short) # {"HI", "CODE"} |
Regex Matching
regex.py
Python
| 1 | import re |
| 2 | |
| 3 | # Parse log lines with walrus |
| 4 | log_lines = [ |
| 5 | "2024-01-15 10:30:00 ERROR Database connection failed", |
| 6 | "2024-01-15 10:31:00 INFO Request processed", |
| 7 | "2024-01-15 10:32:00 WARN High memory usage", |
| 8 | "Invalid log line", |
| 9 | "2024-01-15 10:34:00 ERROR Timeout exceeded", |
| 10 | ] |
| 11 | |
| 12 | pattern = re.compile(r"(\d{4}-\d{2}-\d{2})\s+(\d{2}:\d{2}:\d{2})\s+(\w+)\s+(.*)") |
| 13 | |
| 14 | # Without walrus — repeated match check |
| 15 | errors = [] |
| 16 | for line in log_lines: |
| 17 | match = pattern.match(line) |
| 18 | if match: |
| 19 | date, time, level, msg = match.groups() |
| 20 | if level == "ERROR": |
| 21 | errors.append(f"{date} {time}: {msg}") |
| 22 | |
| 23 | # With walrus — inline match and extract |
| 24 | errors = [ |
| 25 | f"{date} {time}: {msg}" |
| 26 | for line in log_lines |
| 27 | if (match := pattern.match(line)) |
| 28 | if (date, time, level, msg) == match.groups() |
| 29 | if level == "ERROR" |
| 30 | ] |
| 31 | |
| 32 | # Extract all emails from text |
| 33 | text = "Contact alice@example.com or bob@test.org for info" |
| 34 | emails = re.findall(r"[\w.]+@[\w.]+\.[a-z]+", text) |
| 35 | |
| 36 | # Find and process matches |
| 37 | for email in (m := re.finditer(r"[\w.]+@([\w.]+)", text)): |
| 38 | domain = m.group(1) |
| 39 | print(f"Domain: {domain}") |
| 40 | |
| 41 | # Compile and match in one step |
| 42 | if (match := re.fullmatch(r"\d{3}-\d{3}-\d{4}", "555-123-4567")): |
| 43 | print(f"Valid phone: {match.group()}") |
| 44 | |
| 45 | # Multi-pattern matching |
| 46 | def parse_date(text): |
| 47 | for fmt, pattern in [ |
| 48 | ("ISO", r"(\d{4})-(\d{2})-(\d{2})"), |
| 49 | ("US", r"(\d{2})/(\d{2})/(\d{4})"), |
| 50 | ("EU", r"(\d{2})\.(\d{2})\.(\d{4})"), |
| 51 | ]: |
| 52 | if (m := re.match(pattern, text)): |
| 53 | return fmt, m.groups() |
| 54 | return None, None |
| 55 | |
| 56 | fmt, parts = parse_date("2024-01-15") |
| 57 | print(f"Format: {fmt}, Parts: {parts}") # Format: ISO, Parts: ('2024', '01', '15') |
ℹ
info
Walrus with regex is especially powerful in list comprehensions — you can match, extract, and filter in a single pass without temporary variables or nested if-blocks.
Performance Implications
performance.py
Python
| 1 | import timeit |
| 2 | |
| 3 | # Walrus avoids redundant function calls |
| 4 | def expensive(): |
| 5 | # Simulate expensive computation |
| 6 | return sum(range(1_000_000)) |
| 7 | |
| 8 | # Without walrus — calls expensive() twice when condition is True |
| 9 | def without_walrus(): |
| 10 | if expensive() > 0: |
| 11 | return expensive() * 2 |
| 12 | return 0 |
| 13 | |
| 14 | # With walrus — calls expensive() once |
| 15 | def with_walrus(): |
| 16 | if (result := expensive()) > 0: |
| 17 | return result * 2 |
| 18 | return 0 |
| 19 | |
| 20 | # Benchmark |
| 21 | t1 = timeit.timeit(without_walrus, number=100) |
| 22 | t2 = timeit.timeit(with_walrus, number=100) |
| 23 | print(f"Without walrus: {t1:.3f}s") |
| 24 | print(f"With walrus: {t2:.3f}s") |
| 25 | print(f"Speedup: {t1 / t2:.1f}x") |
| 26 | |
| 27 | # Real-world example: dictionary lookup with fallback |
| 28 | cache = {} |
| 29 | |
| 30 | def get_or_compute(key): |
| 31 | # Without walrus: |
| 32 | if key in cache: |
| 33 | value = cache[key] |
| 34 | else: |
| 35 | value = expensive_computation(key) |
| 36 | cache[key] = value |
| 37 | return value |
| 38 | |
| 39 | # With walrus: |
| 40 | # return cache[key] if key in cache else (cache.setdefault(key, expensive_computation(key))) |
| 41 | |
| 42 | # Avoid repeated len() calls |
| 43 | items = list(range(1_000_000)) |
| 44 | |
| 45 | def process_without(): |
| 46 | results = [] |
| 47 | for i in range(len(items)): # len called every iteration |
| 48 | if i < len(items) // 2: |
| 49 | results.append(items[i]) |
| 50 | return results |
| 51 | |
| 52 | def process_with(): |
| 53 | results = [] |
| 54 | n = len(items) |
| 55 | for i in range(n): # len called once |
| 56 | if i < n // 2: |
| 57 | results.append(items[i]) |
| 58 | return results |
✓
best practice
The walrus operator's main performance benefit is avoiding redundant function calls. If your function is cheap (like len()), the readability improvement is more important than any micro-optimization.
When Not to Use Walrus
anti_patterns.py
Python
| 1 | # Anti-pattern: Walrus makes code less readable |
| 2 | # BAD — nested walrus in complex expression |
| 3 | if (a := (b := compute()) > 0) and (c := process(b)): |
| 4 | result = a + c |
| 5 | |
| 6 | # GOOD — separate statements are clearer |
| 7 | b = compute() |
| 8 | if b > 0: |
| 9 | a = b > 0 |
| 10 | c = process(b) |
| 11 | result = a + c |
| 12 | |
| 13 | # Anti-pattern: Using walrus when assignment is obvious |
| 14 | # BAD — unnecessary walrus |
| 15 | x = (y := 10) # just use: x = y = 10 |
| 16 | |
| 17 | # BAD — walrus adds nothing |
| 18 | for i in (n := range(10)): |
| 19 | pass # n serves no purpose |
| 20 | |
| 21 | # Anti-pattern: Side effects in conditions |
| 22 | # BAD — side effect hidden in condition |
| 23 | if (result := api_call()).status == 200: |
| 24 | process(result) |
| 25 | |
| 26 | # GOOD — explicit is better |
| 27 | result = api_call() |
| 28 | if result.status == 200: |
| 29 | process(result) |
| 30 | |
| 31 | # Anti-pattern: Multiple walrus in one expression |
| 32 | # BAD — too many assignments in one line |
| 33 | if (a := f()) and (b := g(a)) and (c := h(b)): |
| 34 | pass |
| 35 | |
| 36 | # GOOD — step by step |
| 37 | a = f() |
| 38 | if a: |
| 39 | b = g(a) |
| 40 | if b: |
| 41 | c = h(b) |
| 42 | |
| 43 | # Good use cases summary: |
| 44 | # ✓ while loops reading input |
| 45 | # ✓ regex matching + filtering |
| 46 | # ✓ avoiding redundant function calls |
| 47 | # ✓ list comprehension with expensive checks |
| 48 | # ✗ simple assignments (use = instead) |
| 49 | # ✗ complex nested expressions |
| 50 | # ✗ side effects in conditions |
$Blueprint — Engineering Documentation·Section ID: PYTHON-WALRUS·Revision: 1.0