LanguageControl Flow

if / elif / else

def classify(n):
  if n < 0:
    return "negative"
  elif n == 0:
    return "zero"
  else:
    return "positive"
 
for x in [-3, 0, 7]:
  print(classify(x))
negative
zero
positive

while

n, total = 5, 0
while n > 0:
  total += n
  n -= 1
print(total)
15

while … else

The else runs if the loop completes without break.

x = 0
while x < 3:
  x += 1
else:
  print("loop finished cleanly")
loop finished cleanly

for

Iterates anything that produces a sequence: list, tuple, dict, set, range, string, generator.

for ch in "abc":
  print(ch)
a
b
c
# Tuple unpacking in the loop variable
pairs = [("a", 1), ("b", 2), ("c", 3)]
for key, value in pairs:
  print(key, value)
a 1
b 2
c 3
# Star pattern works too
for first, *rest in [[1, 2, 3], [4, 5, 6, 7]]:
  print(first, rest)
1 [2, 3]
4 [5, 6, 7]

break and continue

for i in range(10):
  if i == 5:
    break
  if i % 2 == 0:
    continue
  print(i)
1
3

for … else

Runs when the loop exhausts its iterator (no break).

for i in range(3):
  pass
else:
  print("done")
done

match / case

Subset supported: literal patterns, capture variables, _ wildcard, OR (|), guards (if), sequence patterns with *rest.

Sequence-pattern items must be literals (int / float / str / True / False / None), capture names, or _. Nested sequences (case [[a, b], c]:), mapping patterns ({"key": x}), class patterns (Point(x=0)), and as captures are unsupported; use chained if / elif instead.

def classify(p):
  match p:
    case 0:
      return 'zero'
    case 1 | 2 | 3:
      return 'small'
    case n if n < 0:
      return 'negative'
    case [x, y] if x == y:
      return 'diagonal'
    case [first, *middle, last]:
      return f'span {first}..{last}'
    case _:
      return 'other'
 
print(classify(0))
print(classify(2))
print(classify(-7))
print(classify([3, 3]))
print(classify([1, 2, 3, 4, 5]))
zero
small
negative
diagonal
span 1..5
def describe(n):
  match n:
    case 0:
      return "zero"
    case 1:
      return "one"
    case _:
      return "many"
 
for x in [0, 1, 2, 99]:
  print(describe(x))
zero
one
many
many

try / except / else / finally

def safe_div(a, b):
  try:
    return a / b
  except ZeroDivisionError:
    return None
 
print(safe_div(10, 2))
print(safe_div(10, 0))
5.0
None
# Multiple handlers and finally
try:
  x = int("abc")
except ValueError:
  x = -1
finally:
  print("cleanup")
print(x)
cleanup
-1
# Bare except catches everything
try:
  raise "boom"
except:
  print("caught")
caught

raise

def positive(n):
  if n < 0:
    raise ValueError
  return n
 
try:
  positive(-1)
except ValueError:
  print("rejected")
rejected

raise X from Y raises X. The from clause parses and the cause evaluates, but __cause__ / __context__ aren’t preserved; only X reaches the handler.

try:
  raise ValueError from KeyError
except ValueError:
  print("caught the ValueError")
caught the ValueError

Handlers match on class and declared parents. except Exception catches ValueError, RuntimeError, KeyError, etc:

try:
  raise RuntimeError("boom")
except Exception:
  print("subclass caught")
subclass caught

Exception names available

Pre-bound exception classes (with their parent links so except <Parent>: matches subclasses) are listed in Limits and errors, Runtime.

with

with drives the context-manager protocol: evaluate the expression, call __enter__, bind the result to as. On exit, __exit__(exc_type, exc_value, traceback) runs: (None, None, None) on normal completion, live exception info on raise. Truthy return suppresses; falsy propagates. See /language/dunders.

x = [1, 2]
with x as items:
  print(len(items))
print("after")
2
after

Multiple targets:

a, b = "first", "second"
with a as x, b as y:
  print(x, y)
first second

assert

def reciprocal(n):
  assert n != 0, "n must be non-zero"
  return 1 / n
 
print(reciprocal(4))
0.25

A failed assertion raises AssertionError; catchable with except AssertionError, except Exception, or bare except. The optional message after the comma is evaluated only when the assertion fails and becomes the exception’s argument (e.args).

del

Removes a binding from the slot. Works on plain names and indexed positions.

x = 42
del x
try:
  print(x)
except NameError:
  print("gone")
gone