4. Control flow and pattern matching
4.1 if, for, while
status = 503
if status < 300:
kind = "ok"
elif status < 500:
kind = "client error"
else:
kind = "server error"
kind = "ok" if status < 300 else "error" # conditional expression
for i in range(3): # 0, 1, 2
print(i)
for i, host in enumerate(["web1", "web2"], start=1):
print(i, host)
for name, port in zip(["http", "https"], [80, 443], strict=True):
print(name, port) # strict=True: raise if lengths differ
retries = 3
while retries > 0:
retries -= 1
for in Python is always a "for each" over an iterable (Ch 13). There is
no C-style for (i = 0; ...); use range or, better, iterate directly over
the collection.
4.2 break, continue, and for ... else
def find_free_port(used: set[int]) -> int:
for port in range(8000, 8100):
if port in used:
continue
break
else: # runs only if the loop did NOT break
raise RuntimeError("no free port")
return port
print(find_free_port({8000, 8001})) # 8002
4.3 The walrus operator :=
:= assigns inside an expression. Use it where it removes a duplicated
call:
import re
for line in ["user=ada id=7", "noise", "user=linus id=9"]:
if (m := re.search(r"user=(\w+)", line)):
print(m.group(1))
4.4 Structural pattern matching: match
match (3.10+) is not a C switch. It destructures data by shape, like
Rust's match:
def handle(event: dict) -> str:
match event:
case {"type": "click", "x": int(x), "y": int(y)}:
return f"click at {x},{y}"
case {"type": "key", "key": "q" | "Q"}:
return "quit"
case {"type": "key", "key": str(k)} if len(k) == 1:
return f"key {k}"
case {"type": t}:
return f"unknown event {t}"
case _:
return "malformed"
print(handle({"type": "click", "x": 10, "y": 20})) # click at 10,20
print(handle({"type": "key", "key": "Q"})) # quit
print(handle({"type": "scroll"})) # unknown event scroll
print(handle([])) # malformed
It matches sequences, mappings, class instances, literals, and
alternatives (|), with guards (if). It shines on parsed JSON, command
lines, and ASTs. Two traps: a bare name in a pattern binds a variable
instead of comparing (case code: matches anything), and Python does not
check that you covered every case. Use dotted names (case Status.OK:) to
compare against constants.
match ["deploy", "web", "--force"]:
case ["deploy", service, *flags]:
print(service, flags) # web ['--force']
case ["rollback", service]:
print("rollback", service)