단락 평가 — Short-circuit Evaluation
이 토픽을 마치면
Python의 and/or가 내부적으로 어떻게 동작하는지 이해하고, 단락 평가를 활용한 실전 패턴을 쓸 수 있으며, 순서가 중요한 이유를 설명할 수 있습니다.
단락 평가란
**단락 평가(Short-circuit Evaluation)**는 논리 연산에서 결과가 확정되면 나머지를 평가하지 않는 것입니다.
# and: 첫 번째가 False면, 두 번째를 보지 않음False and print("This never runs") # False
# or: 첫 번째가 True면, 두 번째를 보지 않음True or print("This never runs") # True왜 이렇게 동작할까요? 논리적으로 생각하면 당연합니다:
and: 하나라도 False면 전체가 False. 첫 번째가 False면 두 번째를 볼 필요 없음or: 하나라도 True면 전체가 True. 첫 번째가 True면 두 번째를 볼 필요 없음
and의 동작
# and는 첫 번째 falsy 값을 반환하거나, 마지막 값을 반환print(0 and 5) # 0 (0은 falsy → 여기서 멈춤)print("" and "hello") # "" (빈 문자열은 falsy)print(None and 42) # None (None은 falsy)
print(1 and 5) # 5 (1은 truthy → 다음으로 → 5 반환)print("hello" and 42) # 42 (둘 다 truthy → 마지막 값)print(3 and 2 and 1) # 1 (모두 truthy → 마지막 값)print(3 and 0 and 1) # 0 (0에서 멈춤)핵심: and는 첫 번째 falsy 값을 반환합니다. 모두 truthy면 마지막 값을 반환합니다.
Python의 Falsy 값
# 이것들은 모두 False로 평가됨bool(False) # Falsebool(0) # Falsebool(0.0) # Falsebool("") # Falsebool(None) # Falsebool([]) # False (빈 리스트)bool({}) # False (빈 딕셔너리)
# 나머지는 전부 Truebool(1) # Truebool("hello") # Truebool([1, 2]) # Trueor의 동작
# or는 첫 번째 truthy 값을 반환하거나, 마지막 값을 반환print(0 or 5) # 5 (0은 falsy → 다음으로 → 5 반환)print("" or "hello") # "hello" (빈 문자열은 falsy → "hello" 반환)print(None or 42) # 42
print(1 or 5) # 1 (1은 truthy → 여기서 멈춤)print("hi" or "bye") # "hi" (첫 번째가 truthy)print(0 or "" or None) # None (모두 falsy → 마지막 값)print(0 or "" or 42) # 42 (첫 번째 truthy 값)핵심: or는 첫 번째 truthy 값을 반환합니다. 모두 falsy면 마지막 값을 반환합니다.
실전 패턴 — 기본값 설정
# 사용자가 이름을 안 입력했으면 "Guest" 사용username = input("Enter name: ") or "Guest"print(f"Hello, {username}")
# 설정값이 없으면 기본값config = {}db_host = config.get("host") or "localhost"db_port = config.get("port") or 5432or로 기본값을 설정하는 패턴입니다. 왼쪽이 falsy(빈 문자열, None 등)이면 오른쪽 값이 사용됩니다. JavaScript에서도 동일한 패턴을 자주 씁니다.
주의: 0이나 빈 문자열이 유효한 값인 경우에는 이 패턴이 위험합니다.
# Bug: 0도 falsy이므로 기본값이 적용됨count = 0result = count or 10 # 10 (의도: 0이어야 함)
# Fix: 명시적 비교result = count if count is not None else 10 # 0실전 패턴 — 조건부 실행
# and로 조건부 실행 (if 대체)data = [1, 2, 3]data and print(f"Data has {len(data)} items") # 출력됨
empty = []empty and print("This won't print") # 출력 안 됨 (빈 리스트 = falsy)# 안전한 속성 접근user = {"name": "Hoon", "address": None}
# address가 None이면 .get()을 호출하지 않음city = user.get("address") and user["address"].get("city")print(city) # None (and가 None에서 멈춤)순서가 중요한 이유
단락 평가의 핵심은 비싼 연산을 뒤에 배치하는 것입니다.
def is_valid_user(user_id): """DB 조회 — 느린 연산""" print(f"Querying DB for user {user_id}...") return user_id > 0
def has_permission(user_id, action): """권한 확인 — 더 느린 연산""" print(f"Checking permission for {action}...") return True
# Good: 빠른 검사를 먼저user_id = -1if user_id > 0 and is_valid_user(user_id) and has_permission(user_id, "read"): print("Access granted")# Output: (아무것도 출력 안 됨 — user_id > 0이 False이므로 DB 조회 안 함)
# Bad: 느린 검사를 먼저if is_valid_user(user_id) and user_id > 0: print("Access granted")# Output: "Querying DB for user -1..." (불필요한 DB 조회 발생)에러를 방지하는 데에도 유용합니다:
# 리스트가 비어있을 때 인덱싱하면 에러items = []
# Bad: IndexError 발생# if items[0] > 10:
# Good: 빈 리스트 체크를 먼저if items and items[0] > 10: print("First item is large")# items가 falsy(빈 리스트)이므로 items[0]은 실행되지 않음디버깅 시 주의점
단락 평가는 디버깅을 어렵게 만들 수 있습니다.
def check_permission(user): print(f"Checking permission for {user['name']}") return user.get("role") == "admin"
def validate_input(data): print(f"Validating: {data}") return len(data) > 0
user = {"name": "Hoon", "role": "admin"}data = "hello"
if check_permission(user) or validate_input(data): print("Granted")
# 출력:# Checking permission for Hoon# Granted# → validate_input은 실행되지 않음! (print도 안 됨)or에서 첫 번째가 True이면 두 번째 함수는 호출조차 되지 않습니다. 부수 효과(로깅, 카운터 증가)가 있는 함수를 조건식에 넣으면, 실행 여부가 앞의 결과에 따라 달라집니다. 부수 효과가 항상 실행되어야 한다면 조건식 밖에서 먼저 호출하세요.
다른 언어와의 비교
// JavaScript — 동일한 동작
const name = userInput || "Guest";
const port = config.port || 3000;
// JavaScript ES2020: ?? (Nullish Coalescing)
const count = 0 ?? 10; // 0 (??는 null/undefined만 체크)
const count2 = 0 || 10; // 10 (||는 falsy 전체 체크)# Python에는 ?? 연산자가 없음# 대신 명시적 비교를 사용count = 0result = count if count is not None else 10 # 0JavaScript의 ??(Nullish Coalescing)는 null과 undefined만 체크하므로 0이나 빈 문자열이 유효한 값일 때 안전합니다. Python에는 이에 대응하는 연산자가 없으므로, is not None 비교를 명시적으로 써야 합니다.
not과 결합
# not은 단락 평가가 아닌 단순 반전print(not True) # Falseprint(not 0) # Trueprint(not "") # Trueprint(not "hello") # False# 복합 조건에서의 우선순위# not > and > orprint(not True or False) # False (not True → False, False or False → False)print(not (True or False)) # False (True or False → True, not True → False)
# 가독성을 위해 괄호를 명시적으로 사용if (age >= 18) and (not is_banned): print("Access allowed")핵심 정리
| 연산자 | 단락 조건 | 반환 값 |
|---|---|---|
and | 첫 번째가 falsy | 첫 번째 falsy 값, 또는 마지막 값 |
or | 첫 번째가 truthy | 첫 번째 truthy 값, 또는 마지막 값 |
not | (단락 없음) | 항상 True 또는 False |
단락 평가를 이해하면 세 가지를 얻습니다: 기본값 설정 (x or default), 안전한 접근 (items and items[0]), 성능 최적화 (빠른 조건을 앞에). 대부분의 프로그래밍 언어(JavaScript, Java, C++)도 동일한 규칙을 따르므로, 한 번 익히면 어디서든 적용됩니다.