본문으로 건너뛰기
김신건의 로그

[Python] dict: 해시 맵과 삽입 순서 보장

· 수정 · 📖 약 2분 · 608자/단어 #python #dict #dictionary #hash #basics
python dict, 파이썬 딕셔너리, dictionary, hash map, ordered dict

정의

dict는 Python의 해시 맵 자료구조다. Python 3.7부터 삽입 순서를 언어 사양으로 보장한다(이전엔 CPython 3.6 구현 디테일이었음). 평균 O(1) 조회·삽입·삭제. 키는 해시 가능해야 한다(tuple/str/frozenset O, list/dict/set X).

내부 구조 (compact dict, 3.6+)

flowchart TB
    subgraph HT["해시 테이블 (indices 배열)"]
        H0["slot 0: -"]
        H1["slot 1: 2"]
        H2["slot 2: 0"]
        H3["slot 3: -"]
        H4["slot 4: 1"]
    end
    subgraph EA["entries 배열 (삽입 순서)"]
        E0["0: hash=..., key='a', val=1"]
        E1["1: hash=..., key='b', val=2"]
        E2["2: hash=..., key='c', val=3"]
    end
    H2 --> E0
    H4 --> E1
    H1 --> E2
  • 해시 테이블: 슬롯마다 entries 배열의 인덱스만 저장
  • entries 배열: 실제 (hash, key, value) 를 삽입 순서대로 저장
  • 삽입 순서 보장 + 메모리 절약 동시 달성 (이전 CPython 대비 ~50% 절약)

생성

empty = {}
d1 = {"a": 1, "b": 2}
d2 = dict(a=1, b=2)             # 키워드 인수 (str 키만)
d3 = dict([("a", 1), ("b", 2)]) # 튜플 시퀀스
d4 = dict.fromkeys(["a", "b", "c"], 0)  # {'a': 0, 'b': 0, 'c': 0}

# 컴프리헨션
squares = {x: x ** 2 for x in range(5)}

기본 연산

python
d = {"a": 1, "b": 2, "c": 3}

# 조회
print(d["a"])             # KeyError if missing
print(d.get("z"))         # None if missing
print(d.get("z", -1))     # default
print("a" in d)           # 멤버십

# 변경
d["d"] = 4                # 추가/덮어쓰기
d.update({"e": 5, "a": 99})
print(d)

# 삭제
del d["a"]
print(d.pop("b"))         # 반환 후 제거
print(d.popitem())        # 마지막 항목 (LIFO, 3.7+)
print(d)
결과
1
None
-1
True
{'a': 99, 'b': 2, 'c': 3, 'd': 4, 'e': 5}
2
('e', 5)
{'c': 3, 'd': 4}

뷰: keys / values / items

d = {"a": 1, "b": 2}

d.keys()      # dict_keys(['a', 'b'])
d.values()    # dict_values([1, 2])
d.items()     # dict_items([('a', 1), ('b', 2)])

중요: 뷰는 동적이다. dict가 바뀌면 뷰도 즉시 반영된다.

python
d = {"a": 1, "b": 2}
keys = d.keys()
print(list(keys))

d["c"] = 3
print(list(keys))   # 뷰가 업데이트됨
결과
['a', 'b']
['a', 'b', 'c']

순회

d = {"a": 1, "b": 2, "c": 3}

for k in d:                # 키 순회 (기본)
    print(k)

for k, v in d.items():     # 키-값 동시
    print(k, v)

for v in d.values():
    print(v)

WARNING

순회 중 변경 금지: 순회 중 dict를 변경하면 RuntimeError: dictionary changed size during iteration.

# WRONG
for k in d:
    if d[k] < 0:
        del d[k]          # RuntimeError

# CORRECT: 키 리스트로 복사
for k in list(d.keys()):
    if d[k] < 0:
        del d[k]

# 또는 컴프리헨션으로 새 dict 생성
d = {k: v for k, v in d.items() if v >= 0}

defaultdict / Counter

표준 라이브러리 collections의 자주 쓰이는 dict 서브클래스.

python
from collections import defaultdict, Counter

# 그룹핑
words = ["apple", "banana", "cherry", "avocado", "blueberry"]
grouped = defaultdict(list)
for w in words:
  grouped[w[0]].append(w)
print(dict(grouped))

# 빈도 계산
text = "abracadabra"
c = Counter(text)
print(c.most_common(3))
결과
{'a': ['apple', 'avocado'], 'b': ['banana', 'blueberry'], 'c': ['cherry']}
[('a', 5), ('b', 2), ('r', 2)]

Counter 산술

from collections import Counter

a = Counter(["cat", "dog", "cat"])
b = Counter(["dog", "bird"])

print(a + b)        # Counter({'cat': 2, 'dog': 2, 'bird': 1})
print(a - b)        # Counter({'cat': 2})
print(a & b)        # Counter({'dog': 1})   # min(a, b)
print(a | b)        # Counter({'cat': 2, 'dog': 1, 'bird': 1})  # max(a, b)

setdefault와 get

get(k, default)는 dict를 변경하지 않고 기본값 반환. setdefault(k, default)는 키가 없으면 추가까지.

d = {}
v = d.setdefault("count", 0)   # d = {"count": 0}, v = 0
d["count"] += 1                # d = {"count": 1}

# 두 줄 = defaultdict(int)["count"] += 1 한 줄

dict 병합 (3.9+)

PEP 584 도입.

a = {"x": 1, "y": 2}
b = {"y": 99, "z": 3}

merged = a | b               # {'x': 1, 'y': 99, 'z': 3}
a |= b                       # a 직접 갱신

# 3.9 이전:
merged = {**a, **b}

ChainMap: 계층적 조회

collections.ChainMap 은 여러 dict를 체인으로 연결. 앞에서부터 순서대로 키 검색.

from collections import ChainMap

defaults = {"color": "red", "verbose": False}
env     = {"verbose": True}
cli     = {"output": "json"}

config = ChainMap(cli, env, defaults)
print(config["color"])    # "red" (defaults 에서)
print(config["verbose"])  # True (env 에서 오버라이드)
print(config["output"])   # "json" (cli 에서)

# 실전: argparse + 환경변수 + 기본값 계층

해시 가능성

키는 __hash__를 구현해야 한다. 가변 객체(list, dict, set)는 해시 불가능.

{[1, 2]: "x"}              # TypeError: unhashable type: 'list'
{(1, 2): "x"}              # OK
{frozenset([1, 2]): "x"}   # OK

해시값과 동등성은 일관되어야 한다(a == bhash(a) == hash(b)). 직접 만든 클래스를 키로 쓰려면 __eq____hash__를 함께 구현.

class Point:
    def __init__(self, x, y):
        self.x, self.y = x, y

    def __eq__(self, other):
        return (self.x, self.y) == (other.x, other.y)

    def __hash__(self):
        return hash((self.x, self.y))   # tuple 해시 재활용

d = {Point(0, 0): "origin"}

내부 구조 간단히

  • Open addressing + perturbation probing
  • 3.6+ compact dict: 해시 테이블에는 인덱스만, 실제 항목은 별도 배열에 순서대로 저장 → 메모리 절약 + 순서 보존
  • load factor 약 2/3 도달 시 리사이즈
import sys
sys.getsizeof({})              # 64 (3.12 기준)
sys.getsizeof({"a": 1})        # 184

실전 패턴

dict를 클래스처럼: TypedDict

from typing import TypedDict

class User(TypedDict):
    name: str
    age: int

u: User = {"name": "Alice", "age": 30}
# 정적 타입 검사 지원, 런타임엔 일반 dict

조건부 키 포함

# 3.9+ 전: 조건 키 추가하려면 두 번 작성
config = {}
if debug:
    config["verbose"] = True

# 3.9+ 병합 연산자 활용
config = {"host": "localhost"} | ({"verbose": True} if debug else {})

함수형: dict → 변환

inventory = {"apple": 5, "banana": 0, "cherry": 3}

# 0 제거
available = {k: v for k, v in inventory.items() if v > 0}

# 값 변환
doubled = {k: v * 2 for k, v in inventory.items()}

# 키 정렬
sorted_inv = dict(sorted(inventory.items(), key=lambda kv: kv[1], reverse=True))

성능 함정

CAUTION

매우 큰 dict에서 키 정렬이 필요하면 sorted(d) 매번 호출보다 sortedcontainers.SortedDict 검토.

  • 키 충돌이 심한 사용자 정의 객체는 O(n) 가능 → __hash__ 잘 분포되게 구현
  • dict 대신 __slots__ + 클래스: 메모리 1/3, 속도 약간 빠름 (필드 고정 시)
  • dict.get() vs try/except KeyError: 키 존재 확률 낮으면 예외 방식이 더 빠름

관련 위키

이 글의 용어 (5개)
[Python] collections: Counter, deque, defaultdict, OrderedDict, ChainMappython
정의 모듈은 dict/list/tuple/set의 특수화 컨테이너를 제공한다. 표준 라이브러리에 있는 데이터 구조 도구함의 핵심. | 클래스 | 용도 | |--------|---…
[Python] Comprehension: list, dict, set, generatorpython
정의 Comprehension(컴프리헨션)은 iterable로부터 list/set/dict/generator를 선언적·간결하게 만드는 문법이다. 일반 + 보다 30-50% 빠르며…
[Python] dataclass: 자동 생성 메서드를 갖춘 데이터 클래스python
정의 (3.7+, PEP 557)는 클래스에 , , 등 상용구 메서드를 자동 생성해주는 데코레이터다. 데이터 컨테이너 클래스를 한 줄 데코레이터 + 필드 어노테이션만으로 만들 수…
[Python] set, frozensetpython
정의 은 중복 없는 해시 가능 원소의 가변 컬렉션이다. dict의 키 슬롯만 가진 형태로 구현되어 평균 O(1) 멤버십·삽입·삭제를 제공한다. 은 그 불변 버전으로 해시 가능하다…
[Python] typing: 타입 힌트 기초python
정의 Python 타입 힌트(PEP 484)는 런타임에 강제되지 않는 정적 어노테이션이다. , , 같은 타입 체커가 정적 분석에 사용. 런타임은 어노테이션을 무시 (단, 일부 프…

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기