Ch.3 해시맵과 해시셋
Python dict 내부 동작 — 왜 O(1)인가?
우리가 매일 쓰는 dict, 안에서는 무슨 일이 벌어질까?
Python dict는 단순해 보이지만, 내부에는 정교한 해시 테이블이 동작합니다. __hash__ 매직 메서드와 open addressing 변형이 그 비밀입니다.
왜 리스트(list)는 O(n)인데 dict는 O(1)일까?
dict의 내부 배열, __hash__ 프로토콜, 그리고 perturbation probing을 들여다봅시다.
핵심 개념
__hash__
객체의 해시값을 반환하는 Python 매직 메서드
hashable
__hash__를 가진 불변(immutable) 객체
핵심 내용
Python에서 dict의 키가 되려면 hashable 해야 합니다
# hashable 객체 — dict 키 가능
print(hash("hello")) # -1220129488 (실행마다 다를 수 있음)
print(hash(42)) # 42
print(hash((1, 2, 3))) # 529344067
# unhashable 객체 — dict 키 불가!
try:
hash([1, 2, 3]) # list는 mutable!
except TypeError as e:
print(e) # unhashable type: 'list'dict 키가 될 수 있는 타입: str, int, float, tuple, frozenset dict 키가 될 수 없는 타입: list, dict, set 규칙: 불변(immutable) = hashable = 키 가능
dict 내부에는 두 개의 배열이 있습니다. 인덱스 배열 + 엔트리 배열
# Python 3.7+ dict 내부 (개념적)
# 1) 해시 인덱스 테이블 (sparse)
indices = [None, 1, None, 0, None, 2, None, None]
# ↑ ↑ ↑
# 빈 슬롯 entries[0] entries[2]
# 2) 엔트리 배열 (compact, 삽입순 유지)
entries = [
# (hash, key, value)
(3450234, "apple", 100),
(8723091, "banana", 200),
(1293843, "cherry", 300),
]Python 3.7부터 dict가 삽입 순서를 유지하는 이유가 바로 이 compact 구조!
dict["banana"]를 호출하면 내부에서 무슨 일이 벌어질까요?
조회 3단계: ① hash(key) 계산 → 정수 ② 정수 % 테이블크기 → 인덱스 ③ 인덱스 → 엔트리 → 값 반환 각 단계 모두 O(1) → 전체도 O(1)!
데이터가 많아지면 dict는 자동으로 배열을 키웁니다
import sys
d = {}
print(f"빈 dict: {sys.getsizeof(d)} bytes") # 64 bytes
for i in range(6):
d[i] = i
print(f"6개: {sys.getsizeof(d)} bytes") # 232 bytes
for i in range(6, 12):
d[i] = i
print(f"12개: {sys.getsizeof(d)} bytes") # 360 bytes
# → Load Factor 2/3 초과 시 자동 리사이즈!내가 만든 클래스도 __hash__와 __eq__를 정의하면 dict 키가 됩니다
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __hash__(self):
return hash((self.x, self.y)) # 튜플로 해싱
def __eq__(self, other):
return self.x == other.x and self.y == other.y
# 이제 dict 키로 사용 가능!
distance = {
Point(0, 0): 0,
Point(3, 4): 5,
}
print(distance[Point(3, 4)]) # 5__hash__는 '어디서 찾을지', __eq__는 '정말 같은 키인지' 판단합니다
다음 중 dict의 키로 사용할 수 없는 것은?
Python dict는 Load Factor가 ___을 초과하면 리사이즈합니다. (분수로 입력, 예: "1/2")
dict의 내부를 해부했습니다
핵심 용어
__hash__
객체의 해시값(정수)을 반환하는 매직 메서드
hashable
해시값이 변하지 않는 불변 객체 (str, int, tuple 등)
초기 크기
8 슬롯 (빈 dict 생성 시)
리사이즈 조건
Load Factor > 2/3 일 때
확장 배율
현재 크기의 약 2~4배
재해싱
모든 키를 새 배열에 다시 배치
정리 노트
Python dict 내부 동작
dict 키 조건
- hashable
- 불변 객체만 키 가능 (str, int, tuple, frozenset)
- __hash__
- 해시값(정수) 반환 — '어디서 찾을지' 결정
- __eq__
- 동등성 비교 — '정말 같은 키인지' 확인
내부 구조 (Python 3.7+)
- 인덱스 배열
- sparse — 해시값 → 엔트리 위치 매핑
- 엔트리 배열
- compact — (hash, key, value) 삽입순 저장
- 리사이즈
- LF > 2/3 → 배열 2~4배 확장 + 재해싱
면접 핵심: 'dict는 왜 O(1)인가?' → 해시 → 인덱스 → 직접 접근!
시각 자료
핵심 정리
- 1__hash__로 위치 계산, __eq__로 키 확인
- 2compact entries로 삽입 순서 보장 (Python 3.7+)
- 3LF > 2/3이면 자동 리사이즈 → amortized O(1)
퀴즈와 인터랙션으로 더 깊이 학습하세요
play_circle인터랙티브 레슨 시작