본문 바로가기
Language & Tool/Python

🌱논리 연산과 비트 연산(Python)

by 박수무당벌레 2025. 6. 18.

1. 논리 연산

 

참(True), 거짓(False) 값을 다루는 연산 → 조건문, if문에서 비교 연산자(==, !=, <, > 등)를 쓴 판단을 연결하는데에 자주 쓰임

  • bool뿐 아니라 bool 값으로 해석될 수 있는 값들(Truthy, Falsy) 도 사용 가능
  • 전체 값 자체 가 참인지 거짓인지 평가
  • 항상 불리언 결과를 반환하는 게 아니라,
    Python (and, or) 에서는 참/거짓을 판단해서 피연산자 자체 (= 연산에 사용된 원래 값 중 하나를 그대로)를 반환함 
  • 단축 평가 (and, or)
※ Falsy한 값 (False로 간주되는 값들)
- None : 아무 값도 없음
- False : 불리언 False
- 0, 0.0 : 숫자 0
- "" : 빈 문자열
- [], {}, (), set(), range(0) : 빈 리스트, 빈 딕셔너리, 빈 튜플, 빈 집합, 빈 range 객체
- 그외 모든 값들은 Truthy
※ Truthy / Falsy란?
- Truthy : 조건문에서 참(True)으로 평가되는 값
- Falsy : 조건문에서 거짓(False)으로 평가되는 값
즉, bool(x)를 했을 때True가 되면 Truthy False가 되면 Falsy
※ 단축 평가 (Short-circuit)
- a or b에서 a가 True면 b는 평가하지 않음
- a and b에서 a가 False면 b는 평가하지 않음
즉, 첫 번째 값으로 결과가 정해지면 두 번째 평가 생략
※ 논리 연산자는 항상 True/False를 반환하지 않음
Python의 논리 연산자는 True나 False 대신, 왼쪽 또는 오른쪽 값 자체를 그대로 반환함
  print("A" and "B")   # B 출력
  print("" and "B")    # "" 출력
  print("A" or "B")    # A 출력
  print("" or "B")     # B 출력
- and : 왼쪽 값이 Falsy면 그 값을 반환, 그렇지 않으면 오른쪽 값을 반환
- or : 왼쪽 값이 Truthy면 그 값을 반환, 그렇지 않으면 오른쪽 값을 반환

 

 

- 논리 연산자 (Logical Operators)

  • and (논리곱, ∧) : 둘 다 True여야 결과가 True
  • or (논리합, ∨) : 하나라도 True면 True
  • not (논리 부정, ¬) : 반대값 (참 → 거짓, 거짓 → 참)
# 예시
a, b = True, False
print(a and b)  # False
print(a or b)   # True
print(not a)    # False
※ 논리 XOR 은 직접 지원 X
- Python에서 논리값 (True, False) 에 대해 XOR 이라는 논리 연산자가 별도로 존재하지 않음
- 하지만 ^ 연산자는 지원
  → Python은 bool도 int 처럼 취급하기 떄문에 ^ 연산자(비트 XOR)를 사용하여 논리 XOR처럼 작동 가능

 

 

 

2. 비트 연산

 

숫자를 2진수(0과 1)로 바꿔서, 그 비트 단위로 계산하는 방식

  • 정수의 비트 단위 정수형(int) 값만 정상 동작
  • 각 정수 값을 이진수 비트 단위로 비교
  • 비트 연산 결과, 정수(int) 를 반환
  • 단축 평가 지원 ❌, 양쪽 모두 평가

 

- 비트 연산자 (Bitwise Operators)

  • & : 비트 AND → 둘 다 1일 때만 1
  • | : 비트 OR → 하나라도 1이면 1
  • ^ : 비트 XOR (배타적 논리합, ⊕) → 서로 다르면 1
  • ~ : 비트 NOT → 정수 비트 반전 (0 → 1, 1 → 0)
  • <<, >> : 비트를 왼쪽/오른쪽으로 밀기 (곱셈/나눗셈 효과)
# 예시 (5: 0b0101, 3: 0b0011)
print(5 & 3)  # 1 (0b0001)
print(5 | 3)  # 7 (0b0111)
print(5 ^ 3)  # 6 (0b0110)
print(~5)     # -6 (2의 보수 개념)
※ 왜 곱셉, 나눗셈 효과가 나는지?
- 이진수에서 왼쪽으로 한 칸 이동 = 2를 곱하는 것
  → x << n (= x × 2ⁿ) (2의 거듭제곱 곱셈)
- 이진수에서 오른쪽으로 한 칸 이동 = 2로 나누는 것(몫만)
  → x >> n (= x // 2ⁿ) (정수 나눗셈)

 

 

- 연산자별 의미 비교 (정수 vs 집합 vs 논리형)

  • 구현된 위치는 비트연산자 오버로딩(__or__, __and__)
    → 비트연산자 심볼을 오버로딩해서 자기 타입에 맞는 동작 정의
  • int : 비트 단위 연산
  • set : 원소 집합 연산
  • dict : 키 기준 병합/교집합
  • bool : 정수 연산처럼 동작, 결과는 bool 로 유지
    → bool은 사실상 int의 서브클래스  ex) issubclass(bool, int) / isinstance(True, int)  # 둘다 True
  연산 이름 int (정수) set (집합) bool (논리형)
| | OR 비트 OR (합집합) 합집합
& AND 비트 AND 교집합 논리 AND (자동 bool 변환)
^ XOR 비트 XOR 대칭차집합 (공통 원소 제외) 논리 XOR (직접 지원 X, != 사용)
~ NOT 비트 반전 (없음) (없음)

 

 

- Python int  와 비트 연산

  • 정수의 동작 : 수학적 정수 연산(덧셈, 곱셈 등)
  • Python int 는 내부적으로 필요한 만큼 워드(64비트 덩어리) 배열을 붙여서 확장되는 비트 배열 (C/C++는 정수 크기 고정)
    → 임의 정밀도, 크기 제한 ❌(big integer) , 범위 넘어서도 자동 처리 
  • 비트 연산 : 무한 크기의 2진수 비트 배열에 대해 AND, OR, SHIFT 등을 적용하는 것
  • ∴ Python int   가변 길이 비트 배열 처럼 취급 가능, 집합 표현할 때 비트마스크 로 쓰기 매우 편리
    → 실제로는 워드 단위 비트 연산을 반복해서 수행, 사용자 입장에서는 그냥 무한 정밀 비트 연산
     n=100까지도 한 정수 안에 다 들어감
    → 비트 연산은 int 비트셋(Python int를 비트셋처럼 씀)을 이용할 때 상수 시간(O(1))에 가깝게 처리
  • 원소 번호 = 비트 위치로 고정
    연속적이고 작은 범위에서만 효율적
    (예시) 1, 2, 1000 1000번째 비트까지 확보 필요
  • list[bool], set 도 가능
    int 비트마스크보다 느림 (직관적, for 루프 돌면서 OR 처리 O(n) / 오버헤드 + N이 작을 때 훨씬 느림 O(len(a) + len(b)) )
    set의 경우, 원소가 흩어져 있어도 처리 가능 + 범위가 커도 무방
  • 외부라이브러리 bitarray / numpy 배열 구현 가능
    → 내부적으로는 C 구현, 빠름, 대규모
※ 비트마스크 장점
집합 다루는 연산이 모두 한 CPU 명령(비트 연산)으로 끝남
- mask |= (1 << k) : 원소 추가 O(1)
- mask & (1 << k) : 원소 검사 O(1)
- mask1 | mask2 : 합집합 O(1)  (기존 집합 연산은 O(n) )
- n=100일 때 set도 느리진 않지만 , 코드 간결 + 매우 빠름 (상수 시간)
  → 특히 C/C++ 에선 워드 크기가 정해져있어서 n=64 이하일때 한번의 연산으로 모든 집합 연산 가능

 

 

3. 자료형별 지원하는 연산

 

- 연산자 지원 여부 요약

  •  set은 Python 내부적으로 비트 연산자들을 오버로딩(overloading)해서 기호 연산을 집합 연산으로 바꿔놓았기 때문
    → a | b 는 내부적으로 a.__or__(b) 가 호출되고, 이는 곧 a.union(b) 와 같아짐 (merge 연산자)
  연산자 지원 여부
set, frozenset |(합집합), &(교집합), ^(대칭 차집합),
-(차집합),
<=(부분 집합), <(진부분집합), >= (상위 집합), >(진상위 집합),
==, != (집합 동등성 비교),
in, not in (원소 포함 여부
합집합, 교집합, 차집합, 대칭 차집합 등
집합 연산
수학적 개념(중복 X, 순서 X)에 따라 {1, 2} = {2, 1}
산술 +, * 등은 미지원
list, tuple, str +(덧셈), *(반복), ==, !=(비교),
<, <=, >, >= (사전식 비교),
in, not in (포함 여부)
+(연결/병합), *(반복), 비교, in 지원,
집합/비트 연산자 불가
dict ==, != (비교),
in, not in (키 포함 여부)
(수학/비트/집합 연산 X)
==, in, |, &, |=(Python 3.9+) 지원
+ 등은 미지원
int +, -, *, /, //, %, ** (사칙 연산)
|, &, ^, ~ (집합 X, 비트 연산으로 작동),
==, !=, <, >, <=, >= (비교)
and, or, not (논리 연산),
in, not in (포함 여부)
산술 연산자 및 비트 연산자 지원
집합 연산자 없음
bool and, or, not (논리연산),
&, |, ^ (비트 연산),
==, != (비교),
+, - (산술 연산, int처럼 동작),
in, not in (포함 여부)
논리 연산자, 비트 연산자 지원 (비트형 논리 연산)
  → 비트 연산 우선 순위가 낮음 + 논리 연산과의 혼동 주의 필요
집합 연산자 없음

 

 

- dict의 병합 연산 (3.9+)

from functools import reduce

# reduce + | 연산자
dicts = [{"a": 1}, {"b": 2}, {"c": 3}]
merged = reduce(lambda x, y: x | y, dicts)
print(merged)  # {'a': 1, 'b': 2, 'c': 3}

# | 연산자 (3.9+, update() 도 동일 기능)
a = {"x": 1, "y": 2}
b = {"y": 100, "z": 200}
c = a | b   # 병합 결과를 새 dict로 반환
print(c)    # {'x': 1, 'y': 100, 'z': 200}

# ➕ unpacking 방식 (3.5 ~ 3.8)
c = {**a, **b}
print(c)  # {'x': 1, 'y': 100, 'z': 200}
※ dict와 집합 연산
- 요약 : 키 집합에 대한 연산 을 하고, 값은 좌/우 dict 중 하나의 값을 취하는 규칙이 따름
- 3.9+ 부터  |   연산을 지원하지만 집합 연산은 아님
- 키를 기준으로 합치거나 교집합을 취하는 것 (값까지 집합 연산하는 것 X)
  → 집합 연산자를  빌려쓴 것 (집합 연산의 직관을 일부 반영)
 a | b  : 새로운 dict 반환, 합집합처럼 키를 합치되 + 키 충돌 시 오른쪽(b) 값 우선
 a & b  : 공통된 키만 유, 값은 왼쪽(a)의 값