4.1 if / elif / else — 조건 분기 심화
조건문은 프로그램이 상황에 따라 다른 경로를 선택하도록 만드는 핵심 구조입니다. Python의 if / elif / else는 단순해 보이지만, 올바르게 활용하면 복잡한 비즈니스 로직을 명확하고 유지보수하기 쉬운 코드로 표현할 수 있습니다.
기본 if / elif / else 구조
# 기본 문법
score = 85
if score >= 90:
grade = "A"
elif score >= 80:
grade = "B"
elif score >= 70:
grade = "C"
elif score >= 60:
grade = "D"
else:
grade = "F"
print(f"점수 {score}점 → 등급: {grade}") # 점수 85점 → 등급: B
Python에서 조건문 블록은 들여쓰기(indentation) 로 구분합니다. 조건이 True인 첫 번째 블록만 실행되고 나머지는 건너뜁니다.
# elif 없이 if만 연속으로 쓰면 모두 독립적으로 검사됨 (주의!)
x = 10
if x > 5:
print("5보다 큼") # 실행됨
if x > 8:
print("8보다 큼") # 실행됨 — 이전 if와 무관하게 독립 검사
if x > 15:
print("15보다 큼") # 건너뜀
복합 조건: and, or, not
age = 25
has_license = True
is_sober = True
# and: 모든 조건이 True여야 함
if age >= 18 and has_license and is_sober:
print("운전 가능")
# or: 하나라도 True면 됨
is_weekend = False
is_holiday = True
if is_weekend or is_holiday:
print("오늘은 쉬는 날")
# not: 조건 반전
is_raining = False
if not is_raining:
print("우산 필요 없음")
# 복합 조건 — 괄호로 우선순위 명확히
temperature = 30
humidity = 80
if temperature >= 28 and (humidity >= 70 or humidity <= 20):
print("불쾌지수 높음: 습하거나 매우 건조한 더위")
단축 평가(Short-circuit Evaluation)
# and: 첫 번째가 False면 두 번째를 평가하지 않음
def risky_check():
print("risky_check 호출됨")
return True
data = None
# data가 None이면 data.strip()을 호출하지 않아 AttributeError 방지
if data is not None and data.strip():
print("유효한 데이터")
else:
print("데이터 없음") # risky_check는 호출되지 않음
# or: 첫 번째가 True면 두 번째를 평가하지 않음
default_name = ""
username = default_name or "Guest"
print(username) # Guest — 빈 문자열은 falsy
중첩 if 회피 패턴
중첩 if가 깊어지면 코드가 "화살표 코드(Arrow Code)"가 되어 읽기 어려워집니다.
# 나쁜 예: 깊은 중첩
def process_order_bad(order):
if order is not None:
if order.get("user_id"):
if order.get("items"):
if len(order["items"]) > 0:
if order.get("payment_method"):
return "주문 처리 완료"
else:
return "결제 수단 없음"
else:
return "주문 항목 없음"
else:
return "주문 항목 없음"
else:
return "사용자 ID 없음"
else:
return "주문 없음"
조기 반환(Early Return) 패턴
# 좋은 예: 조기 반환으로 중첩 제거
def process_order(order):
if order is None:
return "주문 없음"
if not order.get("user_id"):
return "사용자 ID 없음"
if not order.get("items"):
return "주문 항목 없음"
if len(order["items"]) == 0:
return "주문 항목 없음"
if not order.get("payment_method"):
return "결제 수단 없음"
return "주문 처리 완료"
# 테스트
orders = [
None,
{"user_id": "u1", "items": [], "payment_method": "card"},
{"user_id": "u1", "items": ["item1"], "payment_method": None},
{"user_id": "u1", "items": ["item1"], "payment_method": "card"},
]
for order in orders:
print(process_order(order))
가드 패턴(Guard Clause)
def calculate_discount(user, cart):
# 가드: 사전 조건 검사
if not user:
raise ValueError("사용자 정보가 필요합니다")
if not cart or len(cart) == 0:
return 0.0
# 핵심 로직 — 가드를 통과한 경우만 여기 도달
base_discount = 0.05 # 5%
if user.get("is_premium"):
base_discount += 0.10 # 프리미엄 추가 10%
if len(cart) >= 5:
base_discount += 0.03 # 5개 이상 구매 추가 3%
return min(base_discount, 0.30) # 최대 30%
print(calculate_discount({"is_premium": True}, ["a", "b", "c", "d", "e"])) # 0.18
딕셔너리 디스패치 패턴 (match-case 없이 다중 분기)
# 나쁜 예: 긴 elif 체인
def handle_command_bad(command):
if command == "start":
return "서비스 시작"
elif command == "stop":
return "서비스 중지"
elif command == "restart":
return "서비스 재시작"
elif command == "status":
return "상태 확인"
else:
return f"알 수 없는 명령: {command}"
# 좋은 예: 딕셔너리 디스패치
COMMAND_HANDLERS = {
"start": lambda: "서비스 시작",
"stop": lambda: "서비스 중지",
"restart": lambda: "서비스 재시작",
"status": lambda: "상태 확인",
}
def handle_command(command: str) -> str:
handler = COMMAND_HANDLERS.get(command)
if handler is None:
return f"알 수 없는 명령: {command}"
return handler()
for cmd in ["start", "status", "unknown"]:
print(handle_command(cmd))
# 함수 레퍼런스를 직접 사용하는 패턴
def start_service():
return "서비스 시작"
def stop_service():
return "서비스 중지"
def get_status():
return "상태 확인"
ACTIONS = {
"start": start_service,
"stop": stop_service,
"status": get_status,
}
action = ACTIONS.get("start")
if action:
print(action())
삼항 연산자 (조건 표현식)
# 문법: value_if_true if condition else value_if_false
age = 20
label = "성인" if age >= 18 else "미성년자"
print(label) # 성인
# 중첩 삼항 — 가독성 주의, 2단계 이상은 피하는 것이 좋음
score = 75
grade = "A" if score >= 90 else ("B" if score >= 80 else ("C" if score >= 70 else "D"))
print(grade) # C
# 실용적인 사용: 리스트 컴프리헨션과 조합
numbers = [-3, -1, 0, 2, 5, -7]
abs_values = [x if x >= 0 else -x for x in numbers]
print(abs_values) # [3, 1, 0, 2, 5, 7]
# 함수 호출에서 인수 결정
def connect(host: str, port: int, use_ssl: bool = False):
scheme = "https" if use_ssl else "http"
return f"{scheme}://{host}:{port}"
print(connect("example.com", 443, use_ssl=True)) # https://example.com:443
print(connect("localhost", 8080)) # http://localhost:8080
실전 예제 1: 입력 유효성 검사
from typing import Any
def validate_username(username: Any) -> tuple[bool, str]:
"""사용자명 유효성 검사. (is_valid, error_message) 반환"""
if not isinstance(username, str):
return False, "사용자명은 문자열이어야 합니다"
username = username.strip()
if len(username) < 3:
return False, "사용자명은 3자 이상이어야 합니다"
if len(username) > 20:
return False, "사용자명은 20자 이하여야 합니다"
if not username.replace("_", "").replace("-", "").isalnum():
return False, "사용자명은 영문자, 숫자, _, - 만 허용됩니다"
if username[0].isdigit():
return False, "사용자명은 숫자로 시작할 수 없습니다"
return True, ""
# 테스트
test_cases = ["ab", "valid_user", "1invalid", "user@name", "a" * 21, "good-name"]
for name in test_cases:
valid, msg = validate_username(name)
status = "OK" if valid else f"오류: {msg}"
print(f" '{name}' → {status}")
실전 예제 2: 상태 머신
from dataclasses import dataclass
from typing import Literal
OrderStatus = Literal["pending", "confirmed", "shipped", "delivered", "cancelled"]
@dataclass
class Order:
order_id: str
status: OrderStatus = "pending"
def transition(self, new_status: OrderStatus) -> bool:
"""상태 전이 허용 여부를 if/elif로 관리하는 간단한 상태 머신"""
allowed_transitions = {
"pending": {"confirmed", "cancelled"},
"confirmed": {"shipped", "cancelled"},
"shipped": {"delivered"},
"delivered": set(), # 최종 상태
"cancelled": set(), # 최종 상태
}
if new_status not in allowed_transitions.get(self.status, set()):
print(f" 전이 불가: {self.status} → {new_status}")
return False
print(f" 전이 성공: {self.status} → {new_status}")
self.status = new_status
return True
# 테스트
order = Order("ORD-001")
order.transition("confirmed") # 성공
order.transition("delivered") # 실패: confirmed에서 delivered로 직접 불가
order.transition("shipped") # 성공
order.transition("delivered") # 성공
order.transition("cancelled") # 실패: 최종 상태
print(f" 최종 상태: {order.status}")
고수 팁
1. Truthy / Falsy 값 적극 활용
# Python에서 다음 값들은 False로 평가됨
falsy_values = [None, False, 0, 0.0, 0j, "", [], {}, set(), ()]
# 명시적 비교 대신 truthy/falsy 활용
items = []
# 나쁜 예: if len(items) == 0:
# 좋은 예:
if not items:
print("항목이 없습니다")
name = None
# 나쁜 예: if name is not None and name != "":
# 좋은 예:
if name:
print(f"안녕하세요, {name}님")
2. 비교 연산자 체이닝
# Python은 수학처럼 연쇄 비교 가능
age = 25
# 나쁜 예: if age >= 18 and age <= 65:
# 좋은 예:
if 18 <= age <= 65:
print("근로 가능 연령")
score = 75
# 여러 조건 동시 체이닝
if 0 <= score <= 100:
print("유효한 점수")
3. any() / all() 활용
permissions = ["read", "write", "execute"]
# 하나라도 있으면
if any(p in permissions for p in ["admin", "write"]):
print("쓰기 권한 있음")
# 모두 있어야
required = ["read", "write"]
if all(p in permissions for p in required):
print("필수 권한 모두 충족")
# 빈 시퀀스 검사
data_sources = [[], [1, 2], [], [3]]
has_any_data = any(data_sources) # 비어 있지 않은 리스트가 하나라도 있으면 True
print(has_any_data) # True
4. 조건 표현식으로 None 처리
# None 병합 패턴 (Python에는 ?? 없음, or 또는 삼항 연산 사용)
config_value = None
default_timeout = 30
timeout = config_value if config_value is not None else default_timeout
# 또는 단순히 (None은 falsy):
timeout = config_value or default_timeout
print(timeout) # 30