본문으로 건너뛰기
Advertisement

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
Advertisement