Python 조건문과 참·거짓

src/content/documents/python/python-conditionals-and-truth-values.json

Truthy와 Falsy

조건에는 TrueFalse뿐 아니라 거의 모든 객체를 넣을 수 있다. 조건에서 거짓으로 평가되는 값을 Falsy, 참으로 평가되는 값을 Truthy라고 부른다.

분류

대표 값

bool 결과

없음·거짓 상수

None, False

False

숫자 0

0, 0.0, 0j

False

빈 컨테이너

'', (), [], {}, set(), range(0)

False

그 밖의 일반적인 값

1, 'data', [0], 객체 인스턴스

True

password = ""

if password: # 하나라도 입력되면 참
    print("비밀번호 입력 완료")
else:
    print("비밀번호를 입력하세요.")
비밀번호를 입력하세요.

사용자 정의 객체는 기본적으로 참이다. 클래스가 __bool__()에서 False를 반환하거나 __len__()이 0을 반환하도록 정의하면 거짓으로 평가할 수 있다. 입문 단계에서는 bool(value)로 결과를 직접 확인하면 된다.

# __bool__() 안 쓴 경우
class Dog1:
    pass

dog1 = Dog1() # 객체 생성

if dog1: # 객체는 기본적으로 True(참)로 판단된다.
    print("안씀 : 참")
else:
    print("안씀 : 거짓")


# __bool__() 쓴 경우
class Dog2:
    def __bool__(self): # 객체를 False(거짓)로 판단하도록 직접 지정
        return False

dog2 = Dog2()

if dog2: # __bool__()이 False를 반환하므로 거짓이 된다.
    print("사용함 : 참")
else:
    print("사용함 : 거짓")
안씀 : 참
사용함 : 거짓
# __len__() 안 쓴 경우
class Box1:
    pass

box1 = Box1() # 객체 생성

if box1: # 객체는 기본적으로 True(참)로 판단된다.
    print("안씀 : 참")
else:
    print("안씀 : 거짓")


# __len__() 쓴 경우
class Box2:
    def __len__(self): # 길이가 0이면 False(거짓)로 판단된다.
        return 0 # 객체의 길이를 0으로 지정

box2 = Box2()

if box2:
    print("사용함 : 참")
else:
    print("사용함 : 거짓")
안씀 : 참
사용함 : 거짓

if·elif·else로 데이터 품질 분류하기

score = 75

if score >= 90:
    print("A")
elif score >= 80:
    print("B")
elif score >= 70:
    print("C")
else:
    print("F")
C

조건은 위에서 아래 순서대로 검사한다. 하나의 조건이 참이 되면 나머지 elifelse는 실행하지 않는다. 따라서 범위가 큰 조건부터 작은 조건 순서로 작성해야 원하는 결과를 얻을 수 있다.

and·or·not과 단락 평가(short-circuit evaluation)

x and y는 x가 거짓이면 y를 평가하지 않는다. x or y는 x가 참이면 y를 평가하지 않는다. 이를 단락 평가(short-circuit evaluation)라고 한다. not x는 x의 truth value를 뒤집은 실제 bool 값을 반환한다.

x

y

결과

x and y

x and y

거짓

거짓

x and y

거짓

참이든 거짓이든 결과는 거짓이므로 확인안함

거짓

x or y

거짓

x or y

참이든 거짓이든 결과는 참이므로 확인안함

def check(label, result):
    print(label)
    return result

print(check("left-and", False) and check("right-and", True))
print(check("left-or", True) or check("right-or", False))
left-and
False
left-or
True

오른쪽 호출의 문구가 출력되지 않았다. 이 특성으로 x is not None and x > 0처럼 먼저 안전 조건을 확인할 수 있다. 다만 오른쪽 표현식에 반드시 일어나야 하는 부작용을 넣고 단락 평가에 기대는 코드는 피한다.

and와 or는 bool이 아니라 피연산자를 반환한다

print([] or [1])
print([] and [1])
print("cached" or "fresh")
print("cached" and "fresh")
[1]
[]
cached
fresh

or는 첫 번째 Truthy 피연산자를, 없다면 마지막 피연산자를 반환한다. and는 첫 번째 Falsy 피연산자를, 모두 참이면 마지막 피연산자를 반환한다. 단락 평가가 ‘오른쪽을 실행하는가’를 설명한다면, 이 규칙은 ‘표현식의 최종 값이 무엇인가’를 설명한다.

or 기본값 패턴의 함정

config = {"retry": 0, "label": ""}

print(config.get("retry") or 3)
print(config.get("label") or "untitled")
3
untitled

이 패턴은 키가 없을 때뿐 아니라 값이 0, 빈 문자열, 빈 리스트, False일 때도 대체한다. 이런 값도 유효한 설정이라면 ‘None만 대체’하는 조건을 명시한다.

retry = config.get("retry")
retry = 3 if retry is None else retry
print(retry)
0

==·is·in은 질문이 다르다

연산

질문

대표 예

==, !=

값이 같은가·다른가

score == 0

is, is not

같은 identity의 객체인가

value is None

in, not in

컨테이너에 구성원이 있는가

'score' in row

작은 정수나 문자열이 우연히 같은 객체로 재사용되는지에 기대지 않는다. 값 비교는 ==를 사용하고, 싱글턴인 None과의 비교는 is None 또는 is not None을 사용한다.

조건 표현식과 중첩 조건 읽기

result = value_if_true if condition else value_if_false는 둘 중 하나의 값을 고르는 짧은 표현식에 적합하다. 조건이 여러 단계이거나 각 분기에서 여러 작업을 한다면 일반 if 문이 더 읽기 쉽다. 조건 표현식을 연속으로 중첩해 한 줄에 모든 규칙을 넣지 않는다.

중첩 if가 깊어지면 먼저 잘못된 경우를 처리하고 함수에서 반환하는 guard clause를 검토한다. 아직 구현하지 않은 분기에는 문법상 빈 블록을 허용하지 않으므로 임시로 pass를 둘 수 있지만, 완성 코드에서는 의도한 동작이나 명시적 오류로 교체한다.

실전 데이터 검증 예제

def validate(row):
    required = ("id", "score")
    missing = [key for key in required if key not in row]

    if missing:
        return f"missing: {', '.join(missing)}"
    if row["score"] is None:
        return "score is None"
    if not 0 <= row["score"] <= 100:
        return "score out of range"
    return "ok"

rows = [
    {"id": 1},
    {"id": 2, "score": None},
    {"id": 3, "score": 0},
]

for row in rows:
    print(validate(row))
missing: score
score is None
ok

0은 Falsy지만 유효한 점수이므로 if not row['score']로 누락을 판단하지 않았다. 키 존재, None, 범위를 서로 다른 질문으로 분리하면 데이터 품질 규칙이 명확해진다.

참고 자료

댓글 0

댓글을 불러오는 중…