모던 파이썬 기초 #3 제어 흐름 — if, while, for, match-case
#2 변수/기본 타입과 타입 힌트에서 만든 변수들을 가지고, 이번에는 흐름을 만들어 봅니다. if, while, for, 그리고 3.10에서 들어온 match-case까지 다룹니다.
파이썬 흐름 제어의 큰 특징은 두 가지입니다.
- 블록은 들여쓰기로 구분합니다. 중괄호
{}가 없습니다. - 콜론(
:)으로 헤더를 끝냅니다.if x > 0:처럼.
그래서 들여쓰기가 곧 문법입니다. 일반적으로 공백 4칸을 씁니다 (PEP 8). 탭과 스페이스를 섞어 쓰면 에러가 납니다.
if / elif / else
#
다른 언어와 가장 비슷합니다. 괄호가 없는 게 차이.
score = 85
if score >= 90:
grade = "A"
elif score >= 80:
grade = "B"
elif score >= 70:
grade = "C"
else:
grade = "F"
print(grade) # Belse if가 아니라 elif 입니다. 왜 합쳤는지는 역사적 이유고, 그냥 외우면 됩니다.
한 줄짜리 — 삼항 연산자 #
C 계열의 cond ? a : b와 다릅니다. 파이썬은 a if cond else b 형태입니다.
status = "성인" if age >= 19 else "미성년"자연어 가까운 순서라 처음에는 어색하지만, 익숙해지면 읽기 좋습니다.
진리값과 조건 — falsy 활용 #
#2에서 본 falsy 값들을 그대로 활용합니다.
items: list[int] = []
if not items:
print("비어 있음") # 이게 출력됨
# 아래도 같은 의미지만, 위가 더 파이써닉
if len(items) == 0:
print("비어 있음")리스트 비었음 검사로 len(x) == 0을 쓰는 건 보통 not x로 줄입니다.
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
#
조건이 참인 동안 반복.
count = 0
while count < 5:
print(count)
count += 1++는 없습니다. count += 1입니다.
break, continue
#
n = 0
while True:
n += 1
if n % 2 == 0:
continue # 짝수는 건너뜀
if n > 7:
break # 7 넘으면 끝
print(n)
# 1, 3, 5, 7while True + break는 “조건이 복잡해서 위쪽에 적기 어려운” 무한 루프 패턴입니다. 흔히 씁니다.
else 절 — 잘 안 알려진 기능
#
while, for에는 else가 붙을 수 있습니다. “루프가 break 없이 끝났을 때” 실행됩니다.
n = 0
while n < 10:
if n == 99:
break
n += 1
else:
print("break 없이 끝났음") # 이게 출력흔하지는 않지만 “찾는 값이 없어서 다 돌았을 때"를 처리하기에 깔끔한 경우가 있습니다. 너무 많이 쓰면 가독성이 오히려 떨어지니 적당히.
for — 모두 시퀀스 순회
#
다른 언어의 for (i = 0; i < n; i++) 같은 C 스타일은 없습니다. 파이썬의 for는 항상 이터러블을 순회 합니다.
names = ["a", "b", "c"]
for name in names:
print(name)문자열도 이터러블입니다.
for ch in "hello":
print(ch)
# h, e, l, l, orange — 숫자 시퀀스가 필요할 때
#
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 — 여러 리스트 동시 순회
#
names = ["커티스", "스미스", "존"]
ages = [30, 25, 40]
for name, age in zip(names, ages):
print(f"{name}: {age}")길이가 다르면 짧은 쪽에 맞춰 끊깁니다. 길이 안 맞을 때 에러를 내고 싶으면 zip(..., strict=True) (3.10+).
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:에 해당합니다.
여러 값 한 케이스로 — |
#
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를 한곳에 합친 것과 비슷합니다.
딕셔너리 패턴 #
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로 다시 쓰면 길고 어색해집니다.
if isinstance(shape, Circle):
r = shape.radius
print(f"원, 반지름 {r}")
elif isinstance(shape, Square):
s = shape.side
print(f"정사각형, 한 변 {s}")같은 동작인데 타입 체크와 속성 꺼내기가 따로따로입니다. match는 이 두 단계를 합쳐 한 줄로 표현합니다. JSON/이벤트 객체 처리에서 효용이 큽니다.
흐름의 진실 — 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 컬렉션과 컴프리헨션)에서 본격적으로 다룹니다.
정리 #
이번 글에서 본 것:
- 들여쓰기로 블록 — 4칸 공백, 콜론으로 헤더 종료
if/elif/else, 삼항a if cond else bis는 객체 동일성,==는 값 비교,None검사는is Nonewhile의break/continue/elsefor in으로 이터러블 순회 —range,enumerate,zipmatch-case— 단순 분기, OR 패턴, 구조 분해, dict 패턴, 가드, 클래스 매칭- 빈 블록에는
pass
다음 글(#4 컬렉션과 컴프리헨션)에서는 가장 많이 쓰이는 자료 구조 4개 — list, tuple, dict, set과, 그것들을 한 줄로 만드는 컴프리헨션을 다룹니다.