목차
3 장

제어 흐름 — if, while, for, match-case

들여쓰기로 블록을 만드는 흐름 제어, range/enumerate/zip, 그리고 switch와 동작 방식이 다른 match-case 패턴 매칭까지 정리합니다.

2장 변수, 기본 타입과 타입 힌트에서 만든 변수들을 가지고, 본 챕터에서는 흐름을 만들어 보겠습니다. if, while, for, 그리고 3.10에서 들어온 match-case 까지입니다.

뒤에 자주 만나게 될 match-case가 본 챕터의 핵심 포인트입니다. 다른 언어의 switch와는 동작 방식이 달라 처음에는 낯설지만, 한 번 익숙해지면 JSON / 이벤트 / DTO 분기 코드가 짧고 안전해집니다. 13장 패턴 매칭 깊이에서 본격적으로 다시 다룹니다.

파이썬 흐름 제어의 큰 특징은 두 가지입니다.

  1. 블록은 들여쓰기로 구분합니다. 중괄호 {}가 없습니다.
  2. 콜론(:)으로 헤더를 끝냅니다. if x > 0:처럼.

그래서 들여쓰기가 곧 문법입니다. 일반적으로 공백 4칸을 씁니다 (PEP 8). 탭과 스페이스를 섞어 쓰면 에러가 납니다.

if / elif / else #

다른 언어와 가장 비슷합니다. 괄호가 없는 게 차이.

if 기본
score = 85

if score >= 90:
    grade = "A"
elif score >= 80:
    grade = "B"
elif score >= 70:
    grade = "C"
else:
    grade = "F"

print(grade)   # B

else if가 아니라 **elif**입니다. 왜 합쳤는지는 역사적 이유이며, 외워두면 됩니다.

한 줄짜리 — 삼항 연산자 #

C 계열의 cond ? a : b와 다릅니다. 파이썬은 a if cond else b 형태입니다.

삼항
status = "성인" if age >= 19 else "미성년"

자연어 가까운 순서라 처음에는 어색하지만, 익숙해지면 읽기 좋습니다.

진리값과 조건 — falsy 활용 #

2장에서 본 falsy 값들을 그대로 활용합니다.

falsy 조건
items: list[int] = []

if not items:
    print("비어 있음")    # 이게 출력됨

# 아래도 같은 의미지만, 위가 더 파이써닉
if len(items) == 0:
    print("비어 있음")

리스트 비었음 검사로 len(x) == 0을 쓰는 건 보통 not x로 줄입니다.

is== #

이건 주의가 필요한 부분입니다.

is와 == 의 차이
a = [1, 2, 3]
b = [1, 2, 3]
print(a == b)   # True   (값이 같음)
print(a is b)   # False  (다른 객체)

x = None
print(x is None)   # True  ← None 비교는 항상 is

==은 값 비교, is는 객체 동일성 비교입니다. None, True, False는 항상 is로 비교하세요. 다른 값은 ==입니다.

while #

조건이 참인 동안 반복.

while 기본
count = 0
while count < 5:
    print(count)
    count += 1

++는 없습니다. count += 1입니다.

break, continue #

break / continue
n = 0
while True:
    n += 1
    if n % 2 == 0:
        continue       # 짝수는 건너뜀
    if n > 7:
        break          # 7 넘으면 끝
    print(n)
# 1, 3, 5, 7

while True + break는 “조건이 복잡해서 위쪽에 적기 어려운” 무한 루프 패턴입니다. 흔히 씁니다.

else 절 — 잘 안 알려진 기능 #

while, for에는 else가 붙을 수 있습니다. “루프가 break 없이 끝났을 때” 실행됩니다.

while-else
n = 0
while n < 10:
    if n == 99:
        break
    n += 1
else:
    print("break 없이 끝났음")    # 이게 출력

흔하지는 않지만 “찾는 값이 없어서 다 돌았을 때"를 처리하기에 깔끔한 경우가 있습니다. 너무 많이 쓰면 가독성이 오히려 떨어지니 적당히.

for — 모두 시퀀스 순회 #

다른 언어의 for (i = 0; i < n; i++) 같은 C 스타일은 없습니다. 파이썬의 for는 항상 이터러블을 순회 합니다.

for 기본
names = ["a", "b", "c"]
for name in names:
    print(name)

문자열도 이터러블입니다.

문자 하나씩
for ch in "hello":
    print(ch)
# h, e, l, l, o

11장 이터러블, 제너레이터, yield from에서 for in이 어떤 객체에 동작하는지(__iter__ / __next__ 프로토콜) 깊이 다룹니다.

range — 숫자 시퀀스가 필요할 때 #

range
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)

range(start, stop, step)입니다. stop포함되지 않습니다 (반-개방 구간). 이게 처음에 헷갈리지만, 다른 언어 슬라이스와 같은 관례라 곧 익숙해집니다.

enumerate — 인덱스가 필요할 때 #

인덱스가 필요할 때
items = ["사과", "배", "감"]
for i, item in enumerate(items):
    print(f"{i}: {item}")
# 0: 사과
# 1: 배
# 2: 감

for i in range(len(items))items[i]로 꺼내는 건 안티패턴입니다. enumerate 한 단어로 끝납니다.

zip — 여러 리스트 동시 순회 #

zip
names = ["커티스", "스미스", "존"]
ages = [30, 25, 40]

for name, age in zip(names, ages):
    print(f"{name}: {age}")

길이가 다르면 짧은 쪽에 맞춰 끊깁니다. 길이 안 맞을 때 에러를 내고 싶으면 zip(..., strict=True) (3.10+).

strict 모드
for n, a in zip(names, ages, strict=True):
    ...

패턴 매칭 — match-case (3.10+) #

switch와 모양은 비슷한데, 할 수 있는 일이 훨씬 많습니다. 파이썬은 단순 분기가 아니라 패턴 매칭으로 설계됐습니다.

가장 단순한 형태 #

단순 분기
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 _:가 와일드카드(default)입니다. JavaScript의 default:에 해당합니다.

여러 값 한 케이스로 — | #

OR 패턴
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 "기타"

구조 분해 — 여기서부터 중요 #

리스트, 튜플, 딕셔너리의 모양 자체를 패턴으로 쓸 수 있습니다.

구조 분해
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, y해당 위치의 값을 변수에 바인딩 합니다. 매칭과 동시에 변수가 만들어집니다. JavaScript 구조 분해 + switch가 한곳에 합쳐진 형태.

딕셔너리 패턴 #

dict 패턴
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("알 수 없는 이벤트")

특정 키가 존재하고 그 값을 변수로 가져오는 패턴. API 응답 처리에 잘 맞습니다.

가드 — if 조건 추가 #

가드
match (x, y):
    case (a, b) if a == b:
        print(f"동일: {a}")
    case (a, b) if a > b:
        print(f"a 가 큼")
    case _:
        print("그 외")

클래스 패턴 #

클래스 매칭
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}")

타입 검사 + 속성 추출이 한 줄에 들어갑니다.

switch와 동작 방식이 다른 이유 #

match-case는 단순한 다중 분기가 아니라 자료의 모양으로 분기하는 도구입니다. 위 코드를 if-elif로 다시 쓰면 길고 어색해집니다.

match 없이 같은 일
if isinstance(shape, Circle):
    r = shape.radius
    print(f"원, 반지름 {r}")
elif isinstance(shape, Square):
    s = shape.side
    print(f"정사각형, 한 변 {s}")

같은 동작인데 타입 체크와 속성 꺼내기가 따로따로입니다. match는 이 두 단계를 합쳐 한 줄로 표현합니다. JSON / 이벤트 객체 처리에서 효용이 큽니다.

본 챕터에서는 표층만 살펴보겠습니다. 13장 패턴 매칭 깊이에서 시퀀스 / 매핑 / 클래스 패턴의 정확한 규칙과 실전 사용처를 다시 다룹니다.

흐름의 진실 — pass #

블록이 비어 있을 수가 없습니다. 들여쓰기 블록이 없으면 문법 오류입니다. 일단 비워두고 싶을 때 **pass**를 씁니다.

비워두기
def todo():
    pass    # 나중에 구현

if x > 0:
    pass    # 일단 무시
else:
    handle(x)

... (Ellipsis 객체)도 같은 용도로 종종 보이지만, **pass**가 더 관습적입니다.

for도 컴프리헨션이라는 패턴이 있다 #

리스트, 딕셔너리, 세트를 만들 때 for를 한 줄에 압축하는 문법이 있습니다.

간단한 미리보기
squares = [x ** 2 for x in range(5)]
# [0, 1, 4, 9, 16]

다음 4장 컬렉션과 컴프리헨션에서 본격적으로 다룹니다.

연습문제 #

  1. pyright가 설치된 프로젝트에서 def grade(score: int) -> str: 시그니처의 함수를 작성하세요. 90 이상 “A”, 80 이상 “B”, 70 이상 “C”, 그 외 “F"를 반환합니다. if-elif-else 1개match-case 1개 두 버전을 모두 작성하고 동작이 같은지 확인합니다.
  2. dict[str, int | str] 타입의 event 변수에 {"type": "click", "x": 100, "y": 200} 또는 {"type": "key", "code": "Enter"} 또는 {"type": "scroll", "dx": 0, "dy": -10} 셋 중 하나가 들어옵니다. match-case와 dict 패턴으로 분기해 각각 다른 메시지를 출력하는 handle(event) 함수를 작성하세요.
  3. names = ["커티스", "스미스", "존"]ages = [30, 25, 40]enumerate + zip을 조합해 0: 커티스(30), 1: 스미스(25), 2: 존(40)으로 출력하세요. 길이가 다르면 에러가 나도록 strict=True를 적용합니다.

한 줄 요약: 파이썬 흐름 제어는 들여쓰기 + 콜론. if/elif/else, while, for in이 기본이고, for의 동반자 range/enumerate/zip가 일상의 90% 다. match-case는 단순 switch가 아니라 자료 모양으로 분기하는 도구이며, JSON / 이벤트 / 클래스 분기에서 특히 유용하다.

다음 챕터 #

다음 4장 컬렉션과 컴프리헨션에서는 가장 많이 쓰이는 자료 구조 4개 — list, tuple, dict, set과, 그것들을 한 줄로 만드는 컴프리헨션을 다룹니다. 본 챕터의 for가 컴프리헨션의 기반이 됩니다.

X