I pulled the event processor after your truthiness refactor. Good cleanup. But I noticed something in the log-filtering loop. You call re.search, assign the result to match, then check if match on the next line. The variable exists for exactly one purpose: bridging the assignment and the test.
I need to store the result somewhere to use inside the if-block. How else would I get the match object? I can't check and use in one go.
You can now. Python 3.8 added the walrus operator — := — which assigns and evaluates in a single expression. Here is your loop rewritten:
# Before: temporary variable whose only job is the if-check
for line in log_lines:
match = re.search(r'ERROR: (.+)', line)
if match:
process_error(match.group(1))
# After: assign and test in one expression
for line in log_lines:
if match := re.search(r'ERROR: (.+)', line):
process_error(match.group(1))The := binds match inside the condition. If the search returns a match object, it is truthy and the body executes with match already available.
So the variable still exists — it is just bound inside the expression rather than on its own line. Is the scope any different? I want to know if match leaks out of the if-block.
Good question. In a regular if statement, := binds in the enclosing function scope — same as a regular assignment. The difference shows up in comprehensions. In a list comprehension, loop variables are scoped to the comprehension, but walrus assignments leak into the enclosing scope. That is documented and intentional, but it means you have to be aware:
# walrus in a comprehension — 'result' leaks to enclosing scope
filtered = [result for item in items if (result := compute(item))]
# after this line, 'result' holds the last computed valueUse walrus in comprehensions when you need the computed value in both the filter condition and the output expression — you avoid computing it twice.
I have a while loop in the file processor that duplicates the read call. Init before the loop, then repeat inside. That has been bothering me since I wrote it.
That is the classic walrus use case. The read-process pattern:
# Before: duplicated call, two places to change the buffer size
chunk = f.read(8192)
while chunk:
process(chunk)
chunk = f.read(8192)
# After: one call, loop reads as intended
while chunk := f.read(8192):
process(chunk)The loop now says what it means: while reading a chunk produces a non-empty result, process it. The duplication is gone and the intent is in the loop header where it belongs.
I want to see the regex branch case too. I have a classifier that runs three regexes upfront — email, phone, ID — before any branching. All three run even when the first matches.
Walrus collapses that into short-circuiting branches:
def classify_input(text: str) -> dict:
if m := re.match(r'[\w.]+@[\w.]+', text):
return {"type": "email", "value": m.group()}
elif m := re.match(r'\d{3}[-.] \d{3}[-.] \d{4}', text):
return {"type": "phone", "value": m.group()}
elif m := re.match(r'[A-Z]{2}\d{6}', text):
return {"type": "id", "value": m.group()}
return {"type": "unknown", "value": text}If the email regex matches, the phone and ID regexes never run. Your classifier was running all three on every call.
I wrote that two months ago. It processes a thousand inputs a day. I have been paying for two unnecessary regex evaluations on most calls and never thought to question it.
Your classifier has been very thorough. Now — the boundary to watch. Walrus exists to eliminate temporary variables whose only purpose is the next-line test. When the variable has a life beyond the test — used in logging, referenced later — keep the explicit assignment. Clarity is still the goal. Walrus removes accidental complexity, not intentional structure.
The signal is whether the variable is used only inside the conditional body. If it is, walrus collapses two lines into one that says what it means. If it is used outside, the explicit assignment communicates that the value has a wider life.
Exactly. Tomorrow is star unpacking — pulling the first element, the last element, and the middle from a sequence without index arithmetic. You have been writing items[0] and items[1:] in the order processor. You will see what Python was designed to let you write instead.
:= Works in CPythonThe walrus operator is syntactic sugar with real semantic weight. Understanding what CPython does with := clarifies both its power and its restrictions.
Expression vs. statement in Python's grammar. Python's grammar distinguishes expressions (which produce a value) from statements (which perform an action). Before PEP 572, assignment was statement-only: x = 5 is a statement with no value. An expression like f(x = 5) is a keyword argument, not assignment. The walrus operator introduces a new grammar production — NamedExpr: NAME ':=' Expression — that is always an expression. (x := 5) evaluates to 5 and as a side effect binds x in the enclosing scope. This is why := cannot appear as a standalone statement at the top level: Python's parser requires it to be part of a larger expression.
Scope rules for :=. When := appears inside a function, it binds in the enclosing function scope — not in any nested comprehension or lambda scope. This is the key difference from regular comprehension loop variables. In [y for x in items if (y := f(x))], y is bound in the function that contains the comprehension, not inside the comprehension's implicit scope. This was a deliberate choice: the main use case for walrus in comprehensions is inspecting the last computed value after the comprehension, which requires the variable to survive past the comprehension's scope boundary.
The while-read pattern and loop termination. while chunk := f.read(8192): works because f.read() returns an empty bytes object b"" at EOF, and bool(b"") is False. The walrus assigns the return value of f.read() to chunk on every iteration, then evaluates the truthiness of that value to decide whether to continue. This is not a new capability — Python could always do this with explicit loop control — but it is the natural way to express "loop while this call produces a useful result," and the two-line initialization+repeat pattern it replaces has been a Python anti-pattern since the beginning.
Why the PEP was contentious. PEP 572's objection was not that assignment expressions are useless — the while-read and comprehension cases were acknowledged. The objection was that = and := look similar enough to create confusion, especially for readers coming from C where if (x = func()) is a common source of bugs. Python's resolution was restriction by grammar: := must appear inside a larger expression, cannot appear in certain positions (top-level statements, keyword arguments, augmented assignments), and uses a visually distinct operator. The restrictions make it impossible to accidentally use := where you meant =.
The temporary variable is the signal. The correct mental model: if you are writing tmp = f(x) followed immediately by if tmp: and tmp is not used anywhere after the block, that is an accidental temporary. Walrus removes the accident. If tmp is used after the block — logged, returned, or passed somewhere — it is intentional and deserves its own line.
Sign up to write and run code in this lesson.
I pulled the event processor after your truthiness refactor. Good cleanup. But I noticed something in the log-filtering loop. You call re.search, assign the result to match, then check if match on the next line. The variable exists for exactly one purpose: bridging the assignment and the test.
I need to store the result somewhere to use inside the if-block. How else would I get the match object? I can't check and use in one go.
You can now. Python 3.8 added the walrus operator — := — which assigns and evaluates in a single expression. Here is your loop rewritten:
# Before: temporary variable whose only job is the if-check
for line in log_lines:
match = re.search(r'ERROR: (.+)', line)
if match:
process_error(match.group(1))
# After: assign and test in one expression
for line in log_lines:
if match := re.search(r'ERROR: (.+)', line):
process_error(match.group(1))The := binds match inside the condition. If the search returns a match object, it is truthy and the body executes with match already available.
So the variable still exists — it is just bound inside the expression rather than on its own line. Is the scope any different? I want to know if match leaks out of the if-block.
Good question. In a regular if statement, := binds in the enclosing function scope — same as a regular assignment. The difference shows up in comprehensions. In a list comprehension, loop variables are scoped to the comprehension, but walrus assignments leak into the enclosing scope. That is documented and intentional, but it means you have to be aware:
# walrus in a comprehension — 'result' leaks to enclosing scope
filtered = [result for item in items if (result := compute(item))]
# after this line, 'result' holds the last computed valueUse walrus in comprehensions when you need the computed value in both the filter condition and the output expression — you avoid computing it twice.
I have a while loop in the file processor that duplicates the read call. Init before the loop, then repeat inside. That has been bothering me since I wrote it.
That is the classic walrus use case. The read-process pattern:
# Before: duplicated call, two places to change the buffer size
chunk = f.read(8192)
while chunk:
process(chunk)
chunk = f.read(8192)
# After: one call, loop reads as intended
while chunk := f.read(8192):
process(chunk)The loop now says what it means: while reading a chunk produces a non-empty result, process it. The duplication is gone and the intent is in the loop header where it belongs.
I want to see the regex branch case too. I have a classifier that runs three regexes upfront — email, phone, ID — before any branching. All three run even when the first matches.
Walrus collapses that into short-circuiting branches:
def classify_input(text: str) -> dict:
if m := re.match(r'[\w.]+@[\w.]+', text):
return {"type": "email", "value": m.group()}
elif m := re.match(r'\d{3}[-.] \d{3}[-.] \d{4}', text):
return {"type": "phone", "value": m.group()}
elif m := re.match(r'[A-Z]{2}\d{6}', text):
return {"type": "id", "value": m.group()}
return {"type": "unknown", "value": text}If the email regex matches, the phone and ID regexes never run. Your classifier was running all three on every call.
I wrote that two months ago. It processes a thousand inputs a day. I have been paying for two unnecessary regex evaluations on most calls and never thought to question it.
Your classifier has been very thorough. Now — the boundary to watch. Walrus exists to eliminate temporary variables whose only purpose is the next-line test. When the variable has a life beyond the test — used in logging, referenced later — keep the explicit assignment. Clarity is still the goal. Walrus removes accidental complexity, not intentional structure.
The signal is whether the variable is used only inside the conditional body. If it is, walrus collapses two lines into one that says what it means. If it is used outside, the explicit assignment communicates that the value has a wider life.
Exactly. Tomorrow is star unpacking — pulling the first element, the last element, and the middle from a sequence without index arithmetic. You have been writing items[0] and items[1:] in the order processor. You will see what Python was designed to let you write instead.
:= Works in CPythonThe walrus operator is syntactic sugar with real semantic weight. Understanding what CPython does with := clarifies both its power and its restrictions.
Expression vs. statement in Python's grammar. Python's grammar distinguishes expressions (which produce a value) from statements (which perform an action). Before PEP 572, assignment was statement-only: x = 5 is a statement with no value. An expression like f(x = 5) is a keyword argument, not assignment. The walrus operator introduces a new grammar production — NamedExpr: NAME ':=' Expression — that is always an expression. (x := 5) evaluates to 5 and as a side effect binds x in the enclosing scope. This is why := cannot appear as a standalone statement at the top level: Python's parser requires it to be part of a larger expression.
Scope rules for :=. When := appears inside a function, it binds in the enclosing function scope — not in any nested comprehension or lambda scope. This is the key difference from regular comprehension loop variables. In [y for x in items if (y := f(x))], y is bound in the function that contains the comprehension, not inside the comprehension's implicit scope. This was a deliberate choice: the main use case for walrus in comprehensions is inspecting the last computed value after the comprehension, which requires the variable to survive past the comprehension's scope boundary.
The while-read pattern and loop termination. while chunk := f.read(8192): works because f.read() returns an empty bytes object b"" at EOF, and bool(b"") is False. The walrus assigns the return value of f.read() to chunk on every iteration, then evaluates the truthiness of that value to decide whether to continue. This is not a new capability — Python could always do this with explicit loop control — but it is the natural way to express "loop while this call produces a useful result," and the two-line initialization+repeat pattern it replaces has been a Python anti-pattern since the beginning.
Why the PEP was contentious. PEP 572's objection was not that assignment expressions are useless — the while-read and comprehension cases were acknowledged. The objection was that = and := look similar enough to create confusion, especially for readers coming from C where if (x = func()) is a common source of bugs. Python's resolution was restriction by grammar: := must appear inside a larger expression, cannot appear in certain positions (top-level statements, keyword arguments, augmented assignments), and uses a visually distinct operator. The restrictions make it impossible to accidentally use := where you meant =.
The temporary variable is the signal. The correct mental model: if you are writing tmp = f(x) followed immediately by if tmp: and tmp is not used anywhere after the block, that is an accidental temporary. Walrus removes the accident. If tmp is used after the block — logged, returned, or passed somewhere — it is intentional and deserves its own line.