사용자 정의 클래스의 __bool__()이 False 반환 (없으면 __len__() 0)
나머지는 모두 truthy.
def describe(x): if x: # Pythonic return "non-empty / non-zero" return "empty / zero / None"# 명확한 의도가 필요하면 비교 명시if x is None: ... # 정확히 None인지if len(x) == 0: ... # 빈 시퀀스인지if x == 0: ... # 정확히 0인지
함정: 0과 빈 컨테이너
def get_or_default(d, key, default="N/A"): return d.get(key) or default# 의도: 키 없으면 default# 실제: 값이 0/빈 문자열/[]이어도 default로 대체됨get_or_default({"x": 0}, "x") # "N/A" (!!)# 올바른 코드def get_or_default(d, key, default="N/A"): v = d.get(key) return default if v is None else v
a or b는 a가 falsy면 b. 0이나 ""을 유효값으로 다루는 경우 위험.
bool 연산자: and, or, not
short-circuit 평가하고, bool이 아닌 피연산자도 그대로 반환한다.
python
print(True and "hello") # 'hello'print(False and "hello") # False (단축)print("a" or "b") # 'a' (첫 truthy)print("" or "default") # 'default'print(not 0) # Trueprint(not []) # True# 활용config_value = user_value or defaulterrors = errors or []greeting = name and f"Hello {name}"
결과
helloFalseadefaultTrueTrue
any / all
any(iter), all(iter)는 truthy 기반 검사. 빈 iterable 처리:
all([]) # True (모든 원소가 참 - 빈 집합은 vacuous truth)any([]) # False (참인 원소가 없음)# 컴프리헨션과 결합if all(x > 0 for x in nums): ...if any(name.startswith("admin_") for name in users): ...
generator를 받으면 첫 False(any는 첫 True) 발견 즉시 단축.
사용자 클래스의 truthy
class Cart: def __init__(self): self.items = [] def __bool__(self): return bool(self.items) # 비어 있으면 falsycart = Cart()if cart: checkout()
__bool__이 없으면 __len__()이 사용된다. 둘 다 없으면 항상 truthy.
None 타입 힌트
from typing import Optionaldef find_user(uid: int) -> Optional[User]: """uid로 유저 조회, 없으면 None""" ...# Python 3.10+def find_user(uid: int) -> User | None: ...
Optional[X]는 X | None의 별칭.
Truthiness 결정 흐름
객체의 진리값이 결정되는 순서:
flowchart TD
A["if x: 평가 시작"] --> B{"x is None?"}
B -->|Yes| Z["False"]
B -->|No| C{"bool 서브클래스?"}
C -->|Yes| D["__bool__() 반환값"]
C -->|No| E{"__bool__ 정의?"}
E -->|Yes| F["__bool__() 호출"]
E -->|No| G{"__len__ 정의?"}
G -->|Yes| H{"__len__() == 0?"}
H -->|Yes| Z
H -->|No| Y["True"]
G -->|No| Y
F -->|False| Z
F -->|True| Y
D -->|False| Z
D -->|True| Y
패턴 매칭과 None (Python 3.10+)
match 문으로 None 을 명시적으로 처리:
def process(value: int | None) -> str: match value: case None: return "값 없음" case 0: return "영" case int(n) if n > 0: return f"양수: {n}" case int(n): return f"음수: {n}"
중첩 구조에서도 활용:
def describe_user(user: dict | None) -> str: match user: case None: return "사용자 없음" case {"name": str(name), "age": int(age)} if age >= 19: return f"{name} (성인)" case {"name": str(name)}: return f"{name} (미성년자)" case _: return "알 수 없음"
왈러스 연산자와 None
:= (walrus) 는 None 검사와 값 바인딩을 동시에:
import retext = "Error code: 42"# 기존 패턴m = re.search(r"\d+", text)if m: print(m.group())# 왈러스로 한 줄if m := re.search(r"\d+", text): print(m.group()) # 42# 리스트 컴프리헨션에서 None 필터data = [None, 1, None, 2, 3]results = [y for x in data if (y := x) is not None]print(results) # [1, 2, 3]# 딕셔너리 get 과 조합config = {"timeout": 30}if (val := config.get("retry")) is not None: print(f"retry: {val}")
💬 댓글