Control Flow
|
This section documents the current Python release line as published at the official Python documentation, which is the reference these pages are written and verified against. No specific patch version is pinned. This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. This section’s bibliography lists the reference material consulted while preparing these pages. |
Python has the usual conditional and looping constructs, plus, since 3.10, a structural pattern matching
statement (match) that goes well beyond a simple switch. All of it is covered in the tutorial’s
More Control Flow Tools chapter.
if / elif / else and Truthiness
Python has no switch before 3.10 and no ternary keyword — branching is if / elif / else, and every
value has a truth value used wherever a condition is expected:
def describe(n):
if n < 0:
return "negative"
elif n == 0:
return "zero"
else:
return "positive"
An object is falsy if it is None, False, a numeric zero (0, 0.0, 0j), or an empty container
("", (), [], \{}, set(), range(0)) — everything else is truthy:
for value in [0, 1, "", "a", [], [0], None, {}]:
print(value, "->", "truthy" if value else "falsy")
# 0 -> falsy 1 -> truthy '' -> falsy 'a' -> truthy
# [] -> falsy [0] -> truthy None -> falsy {} -> falsy
This means if items: is the idiomatic emptiness check for any container — prefer it over
if len(items) > 0:. The full rule set is documented under
More on Conditions and
Truth Value Testing.
while and for Loops
while repeats as long as its condition stays truthy; for iterates over any iterable — there is no
C-style three-part for:
n = 5
while n > 0:
print(n)
n -= 1
for ch in "abc":
print(ch) # a, b, c
range()
range() generates an integer sequence lazily. Counting loops iterate over it rather than manually
incrementing an index:
for i in range(5): # 0, 1, 2, 3, 4
print(i)
for i in range(2, 10, 2): # start, stop, step -> 2, 4, 6, 8
print(i)
Full parameters and behaviour are documented at
the range type reference.
break, continue, pass
break exits the innermost loop immediately; continue skips to the next iteration; pass is a no-op
statement used where syntax requires one, such as an empty body while stubbing something out:
for n in range(10):
if n == 3:
continue # skip 3
if n == 7:
break # stop entirely at 7
print(n) # 0, 1, 2, 4, 5, 6
def not_implemented_yet():
pass # placeholder body -- syntactically required, does nothing
The Loop else Clause
A for or while loop can carry an else block, executed only if the loop finished without hitting a
break — a Python-specific idiom often used for "search, then report not-found":
def find_factor(n):
for i in range(2, n):
if n % i == 0:
print(f"{n} = {i} * {n // i}")
break
else:
print(f"{n} is prime") # runs only when no break occurred
find_factor(15) # 15 = 3 * 5
find_factor(13) # 13 is prime
The loop else clause is documented alongside the loop statements in
Break
and continue Statements, and else Clauses on Loops.
The match Statement (3.10+)
match compares a subject against a series of case patterns, executing the first one that matches — structural pattern matching, not a value switch. It is specified in
the language reference’s match
statement section.
Literal, Capture, and Wildcard Patterns
def http_status(code):
match code:
case 200:
return "OK"
case 404:
return "Not Found"
case 500 | 502 | 503: # combine literals with '|'
return "Server Error"
case other: # capture pattern: binds 'other' to the value
return f"Unhandled: {other}"
A bare underscore _ is the wildcard pattern: it matches anything without binding a name, and
conventionally goes last as the catch-all:
def sign(n):
match n:
case 0:
return "zero"
case n if n > 0:
return "positive"
case _:
return "negative"
Guards
A case may carry an if guard — extra runtime logic that must also hold, evaluated only after the
pattern itself matches:
def classify(point):
match point:
case (x, y) if x == y:
return "on the diagonal"
case (x, y) if x == 0 or y == 0:
return "on an axis"
case (x, y):
return f"elsewhere: ({x}, {y})"
Class, Sequence, and Mapping Patterns
A sequence pattern destructures lists/tuples by position (rest collects the remainder); a
mapping pattern destructures dicts by key, ignoring any keys not named (equivalent to always carrying
an implicit rest); a class pattern* matches an instance’s type and unpacks its attributes:
from dataclasses import dataclass
@dataclass
class Point:
x: int
y: int
def describe(value):
match value:
case []:
return "empty list"
case [first, *rest]:
return f"starts with {first}, then {rest}"
case { "kind": "circle", "radius": r }:
return f"circle of radius {r}"
case Point(x=0, y=0):
return "origin"
case Point(x=x, y=y):
return f"point at ({x}, {y})"
case _:
return "no match"
describe([1, 2, 3]) # 'starts with 1, then [2, 3]'
describe({"kind": "circle", "radius": 2}) # 'circle of radius 2'
describe(Point(0, 0)) # 'origin'
Dict/mapping patterns use \{ } just like dict literals — this is standard match syntax, not
anything special to this page. The full pattern grammar (OR patterns, AS patterns binding a whole
subpattern, nested class patterns) is in
the same language-reference
section.
if/elif/else vs. match: The Same Decision, Two Ways
Both constructs pick one branch out of several based on the subject’s shape or value — match simply
lets the pattern itself express structure (class, sequence, mapping) that an if chain would otherwise
have to spell out with isinstance() and manual indexing:
# Equivalent if/elif/else
def area_if(shape):
if isinstance(shape, Circle):
return 3.14159 * shape.r ** 2
elif isinstance(shape, Square):
return shape.side ** 2
else:
raise ValueError("unknown shape")
# Equivalent match
def area_match(shape):
match shape:
case Circle(r=r):
return 3.14159 * r ** 2
case Square(side=s):
return s ** 2
case _:
raise ValueError("unknown shape")
Reach for match when a value’s shape — its type, length, or set of keys — drives the branching; a
plain if/elif/else chain remains clearer for simple value or range comparisons. See the tutorial’s
control flow overview for how both fit alongside
loops and function calls.
See Also
-
Functions — defining the functions called from inside these branches and loops.
-
Exceptions — handling errors that
ifchecks andmatchguards can’t rule out ahead of time.