해시 테이블 원리로 딕셔너리의 O(1) 조회를 이해하고 빈도 카운팅·그루핑·디스패치 패턴을 마스터한다.
🎯 이 강의에서 배우는 것
딕셔너리는 파이썬에서 가장 중요한 자료구조입니다. 내부적으로 해시 테이블(hash table)로 구현되어 키 조회가 평균 O(1)입니다. 파이썬의 이름공간(namespace), 클래스의 속성, 함수의 지역 변수 저장에도 딕셔너리가 사용됩니다. 딕셔너리를 완전히 이해하면 파이썬 자체를 더 깊이 이해하게 됩니다.
🔑 해시 테이블의 원리 — O(1) 접근의 비밀
# 해시 테이블 동작 원리 (단순화):
# 1. hash("name") → 정수값 (예: 1234567890)
# 2. 버킷 인덱스 = 1234567890 % capacity
# 3. 해당 버킷에 ("name", "홍길동") 저장
# 4. 조회 시: 같은 hash() → 같은 버킷 → 즉시 찾음 (O(1)!)
print(hash("name")) # 정수값 (실행마다 다름 — 파이썬 해시 랜덤화)
print(hash(42)) # 42 (int는 자기 자신)
print(hash(3.14)) # 322818021289163912 (근사값 기반)
print(hash((1, 2, 3))) # 2528502973977326415 (불변 튜플 해시 가능)
# 같은 값 == 같은 해시 (해시 함수의 필수 속성)
print(hash("hello") == hash("hello")) # True (항상)
# 같은 해시 ≠ 같은 값 (해시 충돌)
# 파이썬은 개방 주소법으로 충돌 해결
# 딕셔너리 생성 방법
d1 = {"name": "홍길동", "age": 30}
d2 = dict(name="홍길동", age=30)
d3 = dict([("name", "홍길동"), ("age", 30)])
d4 = dict.fromkeys(["a", "b", "c"], 0) # {'a': 0, 'b': 0, 'c': 0}
print(d1 == d2 == d3) # True
# Python 3.7+: 삽입 순서 보장
d = {}
d["c"] = 3
d["a"] = 1
d["b"] = 2
print(list(d.keys())) # ['c', 'a', 'b'] — 삽입 순서 유지
🔍 딕셔너리 조회 메서드 — KeyError 방지
person = {"name": "홍길동", "age": 30, "city": "서울"}
# d[key]: 없으면 KeyError
print(person["name"]) # 홍길동
try:
print(person["email"]) # KeyError!
except KeyError as e:
print(f"없는 키: {e}")
# d.get(key, default): 없으면 default 반환 (기본 None)
print(person.get("email")) # None
print(person.get("email", "미등록")) # 미등록
# d.setdefault(key, default): 없으면 default를 설정하고 반환
person.setdefault("email", "noreply@example.com")
print(person["email"]) # noreply@example.com (새로 추가됨)
person.setdefault("name", "이름없음")
print(person["name"]) # 홍길동 (이미 있으면 변경 안 함)
# 딕셔너리 뷰 객체 (view objects)
keys = person.keys() # dict_keys (동적 뷰)
values = person.values()
items = person.items()
person["score"] = 100 # 딕셔너리 수정
print(list(keys)) # ['name', 'age', 'city', 'email', 'score'] ← 자동 반영!
# 순회
for key in person:
print(key) # 키 순회
for key, value in person.items():
print(f"{key}: {value}")
✏️ 딕셔너리 수정 메서드
d = {"a": 1, "b": 2, "c": 3}
# 추가/수정
d["d"] = 4 # 새 키 추가
d["a"] = 10 # 기존 키 값 변경
# update: 딕셔너리 또는 키-값 쌍으로 일괄 수정
d.update({"e": 5, "f": 6}) # 딕셔너리로
d.update(g=7, h=8) # 키워드 인수로
d.update([("i", 9)]) # (키, 값) 쌍 리스트로
# 삭제
del d["a"] # KeyError 가능
removed = d.pop("b") # 삭제 후 값 반환
removed_safe = d.pop("z", 0) # 없으면 0 반환 (KeyError 없음)
last_key, last_val = d.popitem() # 마지막 삽입 항목 삭제 반환
d.clear() # 전체 삭제
# 딕셔너리 합치기
d1 = {"a": 1, "b": 2}
d2 = {"b": 20, "c": 3}
# 방법 1: {**d1, **d2} (Python 3.5+) — d2가 우선
merged1 = {**d1, **d2}
print(merged1) # {'a': 1, 'b': 20, 'c': 3}
# 방법 2: | 연산자 (Python 3.9+)
merged2 = d1 | d2
print(merged2) # {'a': 1, 'b': 20, 'c': 3}
# 방법 3: |= 인-플레이스 업데이트
d1 |= d2
print(d1) # {'a': 1, 'b': 20, 'c': 3}
🏭 딕셔너리 실전 패턴
from collections import defaultdict
# 패턴 1: 빈도 카운팅
text = "the quick brown fox jumps over the lazy dog"
word_count = {}
for word in text.split():
word_count[word] = word_count.get(word, 0) + 1
print(word_count)
# defaultdict를 사용하면 더 간결
word_count2 = defaultdict(int)
for word in text.split():
word_count2[word] += 1
# 패턴 2: 그루핑
students = [
{"name": "홍길동", "grade": "A"},
{"name": "이순신", "grade": "B"},
{"name": "강감찬", "grade": "A"},
{"name": "유관순", "grade": "B"},
]
groups = defaultdict(list)
for s in students:
groups[s["grade"]].append(s["name"])
print(dict(groups)) # {'A': ['홍길동', '강감찬'], 'B': ['이순신', '유관순']}
# 패턴 3: 디스패치 테이블 (if-elif 대체)
def handle_add(a, b): return a + b
def handle_sub(a, b): return a - b
def handle_mul(a, b): return a * b
def handle_div(a, b): return a / b
operations = {
"+": handle_add,
"-": handle_sub,
"*": handle_mul,
"/": handle_div,
}
def calculate(a, op, b):
if op not in operations:
raise ValueError(f"알 수 없는 연산자: {op}")
return operations[op](a, b)
print(calculate(10, "+", 3)) # 13
print(calculate(10, "*", 3)) # 30
# 패턴 4: 캐싱
_cache = {}
def get_user(user_id):
if user_id not in _cache:
# 실제로는 DB 쿼리
_cache[user_id] = {"id": user_id, "name": f"User {user_id}"}
return _cache[user_id]
⚠️ 자주 하는 실수
- 순회 중 딕셔너리 수정:
for k in d: del d[k]는 RuntimeError: dictionary changed size during iteration. 수정이 필요하면for k in list(d.keys()): ...처럼 뷰를 리스트로 변환 후 순회하세요. - d.keys(), d.values()를 리스트로 착각: 이들은 뷰 객체로 원본 딕셔너리와 연동됩니다. 독립적인 리스트가 필요하면
list(d.keys())로 변환하세요. - 같은 해시 != 같은 키: 해시 충돌 가능성 때문에 딕셔너리는
hash(k1) == hash(k2)이더라도k1 == k2를 다시 확인합니다. 커스텀 클래스를 키로 쓸 때__hash__와__eq__를 일관되게 구현해야 합니다.
📝 정리 및 다음 강의 예고
- 해시 테이블:
hash(key)→ 버킷 인덱스 → 평균 O(1) 조회. 해시 가능 객체만 키로 사용. d.get(key, default): KeyError 없이 안전 조회.d.setdefault(k, v): 없을 때만 설정.- Python 3.7+부터 삽입 순서 보장. Python 3.9+에서
|와|=로 병합. - 빈도 카운팅, 그루핑, 디스패치 테이블 — 딕셔너리가 활약하는 핵심 패턴.
다음 강의: 셋(set)과 frozenset — 해시 기반 집합의 수학적 연산과 실전 활용, 중복 제거의 올바른 방법을 배웁니다.
관련 주제
- 해시 테이블 원리
- 해시 충돌과 개방주소법
- get·setdefault 메서드
- 딕셔너리 병합(|)
- 삽입순서 보장
- 빈도카운팅·디스패치 패턴
- 개발·프로그래밍
- 개발·프로그래밍 강의
- 파이썬 기초 40강 — 처음 배우는 프로그래밍
- 무료강의
- 무료 온라인 강의
- NUGUNA
- 누구나
📚 시리즈 전체 공유
파이썬 기초 40강 — 처음 배우는 프로그래밍
이 강의가 속한 시리즈는 총 32강, 모두 무료입니다. 처음부터 배우려는 동료에게 시리즈 전체를 알려 주세요.
댓글
0/1000
불러오는 중...
