Modern Python Basics #3: Control flow — if, while, for, match-case
Building on the variables from #2 Variables, basic types, and type hints, this time we make flow — if, while, for, and the match-case introduced in 3.10.
Two big features of Python’s flow control:
- Blocks are delimited by indentation. No braces
{}. - Headers end with a colon (
:). Likeif x > 0:.
So indentation is grammar. Conventionally four spaces (PEP 8). Mixing tabs and spaces is an error.
if / elif / else
#
Most similar to other languages. The difference is no parentheses.
score = 85
if score >= 90:
grade = "A"
elif score >= 80:
grade = "B"
elif score >= 70:
grade = "C"
else:
grade = "F"
print(grade) # BNot else if — elif. The historical reason doesn’t matter much; just memorize it.
One-liner — ternary operator #
Different from C-style cond ? a : b. Python uses a if cond else b.
status = "성인" if age >= 19 else "미성년"The natural-language order feels strange at first but reads well once familiar.
Truthiness in conditions — using falsy #
We continue using the falsy values from #2.
items: list[int] = []
if not items:
print("비어 있음") # this prints
# Same meaning below, but the above is more Pythonic
if len(items) == 0:
print("비어 있음")For empty-list checks, len(x) == 0 is usually shortened to not x.
is vs ==
#
This needs care.
a = [1, 2, 3]
b = [1, 2, 3]
print(a == b) # True (same value)
print(a is b) # False (different objects)
x = None
print(x is None) # True ← always use `is` for None== compares values; is compares object identity. Always compare None, True, False with is. Use == for everything else.
while
#
Repeat while a condition is true.
count = 0
while count < 5:
print(count)
count += 1There’s no ++. It’s count += 1.
break, continue
#
n = 0
while True:
n += 1
if n % 2 == 0:
continue # skip even
if n > 7:
break # stop after 7
print(n)
# 1, 3, 5, 7while True + break is a common infinite-loop pattern when the condition is too complex to put up top.
The else clause — a lesser-known feature
#
while and for can have an else. It runs “when the loop ended without a break.”
n = 0
while n < 10:
if n == 99:
break
n += 1
else:
print("break 없이 끝났음") # this printsNot common, but a clean fit for “didn’t find what we were looking for after iterating everything.” Overuse hurts readability — use sparingly.
for — always iterate sequences
#
There is no C-style for (i = 0; i < n; i++). Python’s for always iterates an iterable.
names = ["a", "b", "c"]
for name in names:
print(name)Strings are iterable too.
for ch in "hello":
print(ch)
# h, e, l, l, orange — when you need a numeric sequence
#
for i in range(5): # 0, 1, 2, 3, 4
print(i)
for i in range(2, 7): # 2, 3, 4, 5, 6
print(i)
for i in range(0, 10, 2): # 0, 2, 4, 6, 8 (step 2)
print(i)It’s range(start, stop, step). stop is excluded (half-open interval). Confusing at first, but it matches the slice convention from other languages, so it sticks soon.
enumerate — when you need the index
#
items = ["사과", "배", "감"]
for i, item in enumerate(items):
print(f"{i}: {item}")
# 0: 사과
# 1: 배
# 2: 감for i in range(len(items)) then items[i] is an antipattern. enumerate in one word.
zip — iterate multiple lists in parallel
#
names = ["커티스", "스미스", "존"]
ages = [30, 25, 40]
for name, age in zip(names, ages):
print(f"{name}: {age}")If lengths differ, it stops at the shorter one. To raise an error on mismatched lengths, zip(..., strict=True) (3.10+).
for n, a in zip(names, ages, strict=True):
...Pattern matching — match-case (3.10+)
#
It looks like switch, but does much more. Python designed it not as simple branching but as true pattern matching.
The simplest form #
def http_status(code: int) -> str:
match code:
case 200:
return "OK"
case 404:
return "Not Found"
case 500:
return "Server Error"
case _:
return "Unknown"case _: is the wildcard (default). It’s where JavaScript’s default: would go.
Multiple values per case — |
#
def category(code: int) -> str:
match code:
case 200 | 201 | 204:
return "성공"
case 400 | 401 | 403 | 404:
return "클라이언트 오류"
case 500 | 502 | 503:
return "서버 오류"
case _:
return "기타"Destructuring — the real power starts here #
You can use the shape itself of lists, tuples, and dicts as a pattern.
def describe(point: tuple[int, ...]) -> str:
match point:
case ():
return "빈 점"
case (x,):
return f"1D: {x}"
case (x, y):
return f"2D: ({x}, {y})"
case (x, y, z):
return f"3D: ({x}, {y}, {z})"
case _:
return "고차원"
print(describe((1, 2))) # 2D: (1, 2)
print(describe((1, 2, 3))) # 3D: (1, 2, 3)x and y inside each pattern bind values at that position to variables. Variables are created at the same time as matching. Feels like JavaScript destructuring + switch combined.
Dictionary patterns #
event = {"type": "click", "x": 100, "y": 200}
match event:
case {"type": "click", "x": x, "y": y}:
print(f"클릭: ({x}, {y})")
case {"type": "key", "code": code}:
print(f"키: {code}")
case _:
print("알 수 없는 이벤트")Pattern: certain keys exist and their values are bound to variables. Fits API response handling really well.
Guards — adding if conditions
#
match (x, y):
case (a, b) if a == b:
print(f"동일: {a}")
case (a, b) if a > b:
print(f"a 가 큼")
case _:
print("그 외")Class patterns #
class Circle:
def __init__(self, radius: float):
self.radius = radius
class Square:
def __init__(self, side: float):
self.side = side
shape = Circle(5)
match shape:
case Circle(radius=r):
print(f"원, 반지름 {r}")
case Square(side=s):
print(f"정사각형, 한 변 {s}")Type check + attribute extraction in one line.
Why it’s different from switch
#
match-case isn’t simple multi-way branching but a tool for branching by data shape. Rewriting the code above with if-elif gets long and awkward.
if isinstance(shape, Circle):
r = shape.radius
print(f"원, 반지름 {r}")
elif isinstance(shape, Square):
s = shape.side
print(f"정사각형, 한 변 {s}")Same behavior, but type checking and attribute extraction are separate. match collapses the two into a single line. Highly useful for handling JSON/event objects.
A truth about flow — pass
#
A block can’t be empty. Without an indented block, it’s a syntax error. To leave a placeholder, use pass.
def todo():
pass # implement later
if x > 0:
pass # ignore for now
else:
handle(x)You’ll also see ... (Ellipsis) in the same place, but pass is more idiomatic.
for also has a pattern called comprehensions
#
There’s syntax that collapses a for loop into a single line when building lists, dicts, and sets.
squares = [x ** 2 for x in range(5)]
# [0, 1, 4, 9, 16]We cover this in earnest in the next post (#4 Collections and comprehensions).
Wrap-up #
What this post covered:
- Blocks via indentation — 4 spaces, headers end with a colon
if/elif/else, ternarya if cond else bisfor object identity,==for value; checkNonewithis Nonewhilewithbreak/continue/elsefor initerates iterables —range,enumerate,zipmatch-case— simple branching, OR pattern, destructuring, dict pattern, guard, class matchingpassfor empty blocks
In the next post (#4 Collections and comprehensions) we cover the four most-used data structures — list, tuple, dict, set — and comprehensions that build them in a single line.