Python & AI Tutorials Logo
Python 프로그래밍

9. 불(bool) 논리로 조건 결합하기

7장에서 우리는 불(boolean) 값과 비교 연산자를 사용한 단순한 조건에 대해 배웠습니다. 8장에서는 이러한 조건을 if 문과 함께 사용해 의사 결정을 했습니다. 하지만 실제 프로그램에서는 여러 조건을 한꺼번에 검사해야 하는 경우가 많습니다. 사용자가 올바른 비밀번호를 가지고 있고(and) 로그인된 상태여야 접근을 허용해야 할까요? 온도가 너무 덥거나 혹은(or) 너무 추우면 경고를 표시해야 할까요? 파일이 비어 있지 않을(not) 때 진행해야 할까요?

Python은 불 값을 결합하고 수정할 수 있는 세 가지 논리 연산자(logical operator) 를 제공합니다: and, or, not. 이 연산자들은 프로그램에서 복잡한 의사 결정 로직을 표현하기 위한 기본 구성 요소입니다.

9.1) 논리 연산자 and, or, not

세 가지 논리 연산자는 불 값(또는 불로 취급될 수 있는 값)을 사용해 새로운 불 결과를 만들어 냅니다.

9.1.1) and 연산자

and 연산자는 두 피연산자 모두 참일 때만 True 를 반환합니다. 둘 중 하나라도 거짓이면 전체 표현식은 거짓입니다.

python
# 두 조건이 모두 참이어야 합니다
age = 25
has_license = True
 
can_rent_car = age >= 21 and has_license
print(can_rent_car)  # Output: True
 
# 둘 중 하나라도 거짓이면 결과는 False입니다
age = 18
can_rent_car = age >= 21 and has_license
print(can_rent_car)  # Output: False

and 를 엄격한 문지기처럼 생각하면 됩니다. 전체 검사가 성공하려면 모든 조건을 통과해야 합니다.

and의 진리표:

왼쪽 피연산자오른쪽 피연산자결과
TrueTrueTrue
TrueFalseFalse
FalseTrueFalse
FalseFalseFalse

9.1.2) or 연산자

or 연산자는 최소 하나의 피연산자가 참이면 True 를 반환합니다. 두 피연산자가 모두 거짓일 때만 False 를 반환합니다.

python
# 최소 하나의 조건이 참이어야 합니다
is_weekend = True
is_holiday = False
 
can_sleep_in = is_weekend or is_holiday
print(can_sleep_in)  # Output: True
 
# 두 조건 모두 거짓
is_weekend = False
is_holiday = False
can_sleep_in = is_weekend or is_holiday
print(can_sleep_in)  # Output: False

or 는 느슨한 문지기처럼 생각하면 됩니다. 통과하려면 하나의 조건만 만족하면 됩니다.

or의 진리표:

왼쪽 피연산자오른쪽 피연산자결과
TrueTrueTrue
TrueFalseTrue
FalseTrueTrue
FalseFalseFalse

다음은 할인 자격 시스템의 실제 예입니다:

python
# 고객은 학생이거나 OR 노인인 경우 할인을 받습니다
age = 68
is_student = False
 
gets_discount = is_student or age >= 65
print(f"Eligible for discount: {gets_discount}")  # Output: Eligible for discount: True
 
# 다른 고객
age = 30
is_student = False
gets_discount = is_student or age >= 65
print(f"Eligible for discount: {gets_discount}")  # Output: Eligible for discount: False

첫 번째 고객은 학생은 아니지만(한 기준은 충족하지 못하지만) 노인이기 때문에(다른 기준을 충족해서) 자격이 있습니다.

9.1.3) not 연산자

not 연산자는 단항 연산자(unary operator) 입니다(하나의 피연산자에만 작동합니다). 이 연산자는 불 값을 반전시켜 TrueFalse 로, FalseTrue 로 바꿉니다.

python
is_raining = False
is_sunny = not is_raining
print(is_sunny)  # Output: True
 
is_raining = True
is_sunny = not is_raining
print(is_sunny)  # Output: False

not의 진리표:

피연산자결과
TrueFalse
FalseTrue

not 연산자는 어떤 조건의 반대를 검사하고 싶을 때 특히 유용합니다:

python
# 파일이 비어 있지 않은지 확인
file_size = 0
is_empty = file_size == 0
is_not_empty = not is_empty
print(f"File has content: {is_not_empty}")  # Output: File has content: False
 
# 사용자가 로그인되어 있지 않은지 확인
is_logged_in = False
needs_login_prompt = not is_logged_in
print(f"Show login prompt: {needs_login_prompt}")  # Output: Show login prompt: True

9.1.4) 여러 논리 연산자 결합하기

여러 논리 연산자를 한 표현식 안에서 결합해 더 정교한 조건을 만들 수 있습니다:

python
# 온라인 상점: 주문 금액이 $50 이상이거나 또는(OR) 고객이 프리미엄 회원
# 그리고(AND) 상품이 재고에 있을 때 무료 배송
order_total = 45.00
is_premium = True
in_stock = True
 
gets_free_shipping = (order_total >= 50 or is_premium) and in_stock
print(f"Free shipping: {gets_free_shipping}")  # Output: Free shipping: True

이 평가 과정을 따라가 보겠습니다:

  1. order_total >= 50False 입니다 (45.00은 50 이상이 아님)
  2. is_premiumTrue 입니다
  3. False or TrueTrue 로 평가됩니다
  4. in_stockTrue 입니다
  5. True and TrueTrue 로 평가됩니다

다음은 접근 제어(access control)에 관한 또 다른 예입니다:

python
# 사용자가 admin 패널에 접근할 수 있으려면 admin이어야 하고
# AND (내부 네트워크에 있거나 OR VPN을 사용 중이어야 합니다)
is_admin = True
on_internal_network = False
using_vpn = True
 
can_access_admin = is_admin and (on_internal_network or using_vpn)
print(f"Can access admin panel: {can_access_admin}")  # Output: Can access admin panel: True

(on_internal_network or using_vpn) 주위의 괄호를 주목하세요. 이 괄호는 산술 표현식의 괄호와 마찬가지로 평가 순서를 제어하기 때문에 중요합니다.

9.2) 불 표현식에서의 연산자 우선순위 (not, and, or 순서)

괄호 없이 여러 논리 연산자를 결합할 때, Python은 평가 순서를 결정하기 위해 특정 우선순위 규칙을 따릅니다. 이 규칙을 이해하면 올바른 조건을 작성하고 미묘한 버그를 피할 수 있습니다.

9.2.1) 우선순위 계층 구조

Python은 논리 연산자를 다음 순서(높은 우선순위에서 낮은 우선순위 순)로 평가합니다:

  1. not (가장 높은 우선순위)
  2. and (중간 우선순위)
  3. or (가장 낮은 우선순위)

즉, not 이 먼저 평가되고, 그다음 and, 마지막으로 or 가 평가됩니다.

python
# 괄호가 없을 때는 우선순위가 순서를 결정합니다
result = True or False and False
print(result)  # Output: True
 
# Python은 이렇게 평가합니다:
# Step 1: False and False → False (and가 or보다 우선순위가 높음)
# Step 2: True or False → True

좀 더 자세한 예로 단계별로 살펴보겠습니다:

python
is_weekend = False
is_holiday = True
has_work = True
 
# 표현식: not has_work or is_weekend and is_holiday
free_time = not has_work or is_weekend and is_holiday
 
# 평가 순서:
# Step 1: not has_work → not True → False
# Step 2: is_weekend and is_holiday → False and True → False
# Step 3: False or False → False
print(f"Has free time: {free_time}")  # Output: Has free time: False

9.2.2) 가독성을 위한 괄호 사용하기

우선순위 규칙을 이해하고 있더라도, 괄호를 사용하면 코드가 더 명확해지고 실수를 방지할 수 있습니다. 괄호는 기본 우선순위를 무시하고, 당신의 의도를 명확하게 드러냅니다.

python
# 괄호가 없으면 모호할 수 있습니다
result = True or False and False
print(result)  # Output: True
 
# 괄호를 사용하면 명확합니다 - 실제로 무엇을 의미했을까요?
result = (True or False) and False
print(result)  # Output: False
 
result = True or (False and False)
print(result)  # Output: True

이 두 표현식은 서로 다른 결과를 만듭니다! 괄호가 의미를 완전히 바꿔 버립니다.

9.2.3) 비교 연산자와 논리 연산자를 함께 사용하기

비교 연산자(예: <, >, ==, !=)는 논리 연산자보다 더 높은 우선순위 를 가집니다. 즉, 비교가 논리 연산보다 먼저 평가됩니다.

python
age = 25
income = 50000
 
# 비교를 괄호로 둘러쌀 필요가 없습니다
eligible = age >= 18 and income >= 30000
print(f"Eligible for loan: {eligible}")  # Output: Eligible for loan: True
 
# Python은 다음과 같이 평가합니다:
# Step 1: age >= 18 → True
# Step 2: income >= 30000 → True
# Step 3: True and True → True

9.3) 쇼트 서킷(short-circuit) 평가

Python은 andor 가 포함된 불 표현식을 평가할 때 쇼트 서킷 평가(short-circuit evaluation) 를 사용합니다. 이는 최종 결과가 판명되는 순간 평가를 멈추고, 이후 피연산자 평가를 건너뛴다는 뜻입니다. 이 동작은 성능 최적화일 뿐만 아니라 유용한 프로그래밍 기법이기도 합니다.

9.3.1) and가 쇼트 서킷되는 방식

and 연산자에서 왼쪽 피연산자가 False 이면, Python은 전체 표현식이 반드시 False 임을 알게 됩니다(andTrue 를 반환하려면 두 피연산자가 모두 참이어야 하기 때문입니다). 따라서 Python은 오른쪽 피연산자를 전혀 평가하지 않습니다.

python
# 간단한 데모
x = 5
result = x < 3 and x > 10
print(result)  # Output: False
 
# Python의 평가:
# Step 1: x < 3 → 5 < 3 → False
# Step 2: 왼쪽이 False이므로 x > 10은 평가하지 않습니다
# Step 3: False 반환

다음은 쇼트 서킷 평가가 왜 중요한지 보여 주는 실용적인 예입니다:

python
# 어떤 수가 나누어지는지 검사하기 - 0으로 나누기를 피하기
numerator = 100
denominator = 0
 
# 쇼트 서킷 평가 덕분에 안전합니다
# denominator가 0이면 나눗셈이 전혀 수행되지 않습니다
is_divisible = denominator != 0 and numerator % denominator == 0
print(f"Is divisible: {is_divisible}")  # Output: Is divisible: False
 
# 쇼트 서킷 평가가 없다면, 이는 에러를 발생시킬 것입니다:
# denominator = 0
# result = numerator % denominator  # ZeroDivisionError!

denominator != 0False 로 평가되기 때문에, Python은 numerator % denominator 를 평가하지 않습니다. 이 연산은 0으로 나누는 에러를 일으킬 것입니다.

문자열 연산에서의 또 다른 예를 보겠습니다:

python
# 문자열 속성 안전하게 검사하기
text = ""
 
# text가 비어 있지 않고 AND 첫 글자가 대문자인지 검사
# text가 비어 있으면 text[0]에 접근하지 않기 때문에 안전합니다
has_uppercase_start = len(text) > 0 and text[0].isupper()
print(f"Starts with uppercase: {has_uppercase_start}")  # Output: Starts with uppercase: False
 
# 길이 검사를 하지 않고 시도하면:
# text = ""
# result = text[0].isupper()  # IndexError: string index out of range

9.3.2) or가 쇼트 서킷되는 방식

or 연산자에서 왼쪽 피연산자가 True 이면, Python은 전체 표현식이 반드시 True 임을 알게 됩니다(or 는 피연산자 중 하나라도 참이면 충분하기 때문입니다). 따라서 Python은 오른쪽 피연산자를 평가하지 않습니다.

python
# 간단한 데모
x = 15
result = x > 10 or x < 5
print(result)  # Output: True
 
# Python의 평가:
# Step 1: x > 10 → 15 > 10 → True
# Step 2: 왼쪽이 True이므로 x < 5는 평가하지 않습니다
# Step 3: True 반환

9.3.3) 쇼트 서킷 평가의 실용적 활용

에러 피하기:

python
# 리스트 요소를 안전하게 접근하기
numbers = [1, 2, 3]
index = 5
 
# 인덱스가 유효한지 먼저 확인
is_valid = index < len(numbers) and numbers[index] > 0
print(f"Valid and positive: {is_valid}")  # Output: Valid and positive: False
 
# 쇼트 서킷이 없었다면, 이는 크래시를 일으킬 것입니다:
# is_valid = numbers[index] > 0  # IndexError!

여러 조건을 효율적으로 검사하기:

python
# 폼 검증 - 첫 번째 에러에서 멈추기
email = "user@example.com"
password = "pass"
age = 25
 
# 실패 가능성이 높은 순서대로 각 요구 사항을 검사
valid_form = (
    len(email) > 0 and              # 빠른 검사
    "@" in email and                # 빠른 검사
    len(password) >= 8 and          # 빠른 검사
    age >= 18                       # 빠른 검사
)
print(f"Form valid: {valid_form}")  # Output: Form valid: False
# password 길이 검사에서 멈추고, age는 평가하지 않습니다

No

Yes

Yes

No

and Expression

Left Side True?

Return False
Skip Right Side

Evaluate Right Side

Return Right Result

or Expression

Left Side True?

Return True
Skip Right Side

Evaluate Right Side

Return Right Result

9.4) and와 or 연산자가 비-불 피연산자와 함께 반환하는 값, 그리고 흔한 불 표현식 함정

지금까지 우리는 and, or, not 이 불 값과 함께 작동하는 모습을 봤습니다. 하지만 Python의 논리 연산자는 흥미로운 동작을 가지고 있습니다. 이들은 오직 TrueFalse 뿐 아니라 모든 값과 함께 동작할 수 있습니다. 이 동작을 이해하면 더 간결한 코드를 작성하고 흔한 실수를 피하는 데 도움이 됩니다.

9.4.1) 진리성(truthiness)과 거짓성(falsiness) 이해하기 (복습)

7장에서 배운 것처럼, Python은 많은 비-불 값을 불 컨텍스트에서 "참 같은(truthy)" 또는 "거짓 같은(falsy)" 값으로 취급합니다:

falsy 값 (불 컨텍스트에서 False 로 취급됨):

  • False
  • None
  • 0 (어떤 숫자형의 0이든)
  • "" (빈 문자열)
  • [] (빈 리스트)
  • {} (빈 딕셔너리)
  • () (빈 튜플)

truthy 값 (불 컨텍스트에서 True 로 취급됨):

  • True
  • 0이 아닌 어떤 숫자든
  • 비어 있지 않은 문자열
  • 비어 있지 않은 어떤 컬렉션이든
python
# 진리성 데모
if "hello":
    print("Non-empty strings are truthy")  # Output: Non-empty strings are truthy
 
if 0:
    print("This won't print")  # 0은 falsy입니다
else:
    print("Zero is falsy")  # Output: Zero is falsy
 
if [1, 2, 3]:
    print("Non-empty lists are truthy")  # Output: Non-empty lists are truthy

9.4.2) and가 실제로 반환하는 것

and 연산자는 항상 TrueFalse 를 반환하지 않습니다. 대신 자신의 피연산자 중 하나 를 반환합니다:

  • 왼쪽 피연산자가 falsy이면, and 는 (오른쪽을 평가하지 않고) 왼쪽 피연산자를 반환합니다.
  • 왼쪽 피연산자가 truthy이면, and 는 오른쪽 피연산자를 반환합니다.
python
# and는 첫 번째 falsy 값을 반환하거나, 모두 truthy이면 마지막 값을 반환합니다
result = 5 and 10
print(result)  # Output: 10
 
result = 0 and 10
print(result)  # Output: 0
 
result = "hello" and "world"
print(result)  # Output: world
 
result = "" and "world"
print(result)  # Output: (빈 문자열)
 
result = None and "world"
print(result)  # Output: None

이 예제들을 단계별로 살펴보겠습니다:

python
# 예 1: 둘 다 truthy
result = 5 and 10
# Step 1: 5는 truthy이므로 오른쪽을 평가합니다
# Step 2: 오른쪽 값 반환: 10
print(result)  # Output: 10
 
# 예 2: 왼쪽이 falsy
result = 0 and 10
# Step 1: 0은 falsy이므로 즉시 이 값을 반환합니다
# Step 2: 오른쪽은 평가하지 않습니다
print(result)  # Output: 0
 
# 예 3: 둘 다 truthy 문자열
result = "hello" and "world"
# Step 1: "hello"는 truthy이므로 오른쪽을 평가합니다
# Step 2: 오른쪽 값 반환: "world"
print(result)  # Output: world

9.4.3) or가 실제로 반환하는 것

마찬가지로, or 연산자도 자신의 피연산자 중 하나를 반환합니다:

  • 왼쪽 피연산자가 truthy이면, or 는 (오른쪽을 평가하지 않고) 왼쪽 피연산자를 반환합니다.
  • 왼쪽 피연산자가 falsy이면, or 는 오른쪽 피연산자를 반환합니다.
python
# or는 첫 번째 truthy 값을 반환하거나, 모두 falsy이면 마지막 값을 반환합니다
result = 5 or 10
print(result)  # Output: 5
 
result = 0 or 10
print(result)  # Output: 10
 
result = "" or "default"
print(result)  # Output: default
 
result = "hello" or "world"
print(result)  # Output: hello
 
result = None or 0
print(result)  # Output: 0

이 예제들을 단계별로 살펴보겠습니다:

python
# 예 1: 왼쪽이 truthy
result = 5 or 10
# Step 1: 5는 truthy이므로 즉시 이 값을 반환합니다
# Step 2: 오른쪽은 평가하지 않습니다
print(result)  # Output: 5
 
# 예 2: 왼쪽이 falsy
result = 0 or 10
# Step 1: 0은 falsy이므로 오른쪽을 평가합니다
# Step 2: 오른쪽 값 반환: 10
print(result)  # Output: 10
 
# 예 3: 둘 다 falsy
result = None or 0
# Step 1: None은 falsy이므로 오른쪽을 평가합니다
# Step 2: 오른쪽 값 반환: 0 (이 값도 falsy이긴 합니다)
print(result)  # Output: 0

9.4.4) 기본값 제공을 위한 or의 실용적 활용

자주 쓰이는 패턴 중 하나는 or 를 사용해 기본값을 제공하는 것입니다:

python
# 기본값이 있는 사용자 설정
user_theme = ""  # 사용자가 테마를 설정하지 않음
theme = user_theme or "light"
print(f"Theme: {theme}")  # Output: Theme: light
 
user_theme = "dark"
theme = user_theme or "light"
print(f"Theme: {theme}")  # Output: Theme: dark
 
# 설정 값
max_retries = None  # 설정되지 않음
retries = max_retries or 3
print(f"Retries: {retries}")  # Output: Retries: 3
 
max_retries = 5
retries = max_retries or 3
print(f"Retries: {retries}")  # Output: Retries: 5

이 패턴은 왼쪽이 falsy(빈 문자열, None, 0 등)일 때, or 가 오른쪽(기본값)을 반환한다는 점을 이용합니다.

and

or

Yes

No

Yes

No

Logical Operator with Non-Boolean

Operator Type

Left Falsy?

Left Truthy?

Return Left Value

Return Right Value

Return Left Value

Return Right Value

9.4.12) 연산자가 무엇을 반환하는지 요약

각 논리 연산자가 무엇을 반환하는지에 대한 종합적인 요약입니다:

and 연산자:

  • 첫 번째 falsy 피연산자를 반환합니다.
  • 모든 피연산자가 truthy이면 마지막 피연산자를 반환합니다.
  • 쇼트 서킷 평가를 사용합니다(첫 번째 falsy 값에서 멈춤).

or 연산자:

  • 첫 번째 truthy 피연산자를 반환합니다.
  • 모든 피연산자가 falsy이면 마지막 피연산자를 반환합니다.
  • 쇼트 서킷 평가를 사용합니다(첫 번째 truthy 값에서 멈춤).

not 연산자:

  • 항상 불 값(True 또는 False)을 반환합니다.
  • not 은 피연산자를 불로 변환한 다음 그 값을 반전시킵니다.
python
# 세 연산자를 모두 데모
print(5 and 10)           # Output: 10 (둘 다 truthy이므로 마지막 값 반환)
print(0 and 10)           # Output: 0 (첫 번째 falsy 값 반환)
print(5 or 10)            # Output: 5 (첫 번째 truthy 값 반환)
print(0 or 10)            # Output: 10 (첫 번째 falsy이므로 두 번째 평가)
print(not 5)              # Output: False (5는 truthy이므로 not은 False 반환)
print(not 0)              # Output: True (0은 falsy이므로 not은 True 반환)
print(not "")             # Output: True (빈 문자열은 falsy)
print(not "hello")        # Output: False (비어 있지 않은 문자열은 truthy)

이 동작들을 이해하면 더 간결하고 Python다운(Pythonic) 코드를 작성할 수 있습니다. 하지만 항상 가독성을 우선시하세요. 이런 기능을 사용했을 때 코드가 더 이해하기 어려워진다면, 보다 명시적으로 작성하는 편이 낫습니다.


이 장에서는 Python의 and, or, not 연산자를 사용해 단순한 조건을 결합하여 복잡한 불 논리를 구성하는 방법을 살펴보았습니다. 연산자 우선순위, 쇼트 서킷 평가, 그리고 비-불 값과 함께 사용될 때 논리 연산자가 보이는 놀라운 동작에 대해 배웠습니다. 또한 불 표현식을 작성할 때 자주 빠지는 함정과 모범 사례도 살펴보았습니다.

이 도구들을 활용하면 프로그램에서 정교한 의사 결정 로직을 표현할 수 있습니다. 8장에서 배운 if 문과 결합하면, 이제 프로그램이 필요로 하는 거의 모든 조건 로직을 다룰 수 있습니다. 다음 장에서는 어떤 조건에 따라 두 값 중 하나를 선택하는 더 간결한 방법인 조건식(conditional expression)을 살펴보겠습니다.

© 2025. Primesoft Co., Ltd.
support@primesoft.ai