defaultdict
: 조건 없는 딕셔너리의 진화
파이썬에는 수많은 외부 라이브러리가 존재하지만, 표준 라이브러리만 제대로 이해해도 실무에서 충분히 강력한 코드를 작성할 수 있습니다. 이번 글에서는 그중에서도 collections.defaultdict
를 깊이 있게 살펴보겠습니다.
이 글을 통해 단순한 개념 소개를 넘어서, 실제로 언제, 왜, 어떻게 defaultdict
를 써야 하는지를 명확히 이해하게 될 것입니다.
collections시리즈인 첫번째, Counter클래스에 대해 궁금하신 분은 이전 글을 읽어보시길 추천드립니다. Python표준 라이브러리 ① - collections.Counter
1. 기본 개념: defaultdict
란?
defaultdict
는 파이썬 표준 라이브러리 collections
모듈에 포함된 특수한 딕셔너리(dict)의 서브클래스입니다. 일반적인 딕셔너리에서는 존재하지 않는 키에 접근할 경우 KeyError
가 발생하지만, defaultdict
는 자동으로 기본값을 생성해주는 함수(factory function)를 지정할 수 있어 코드가 훨씬 깔끔해지고 오류를 방지할 수 있습니다.
2. 기본 사용법
from collections import defaultdict
d = defaultdict(int)
d['apple'] += 1
print(d) # defaultdict(<class 'int'>, {'apple': 1})
여기서 int()
는 기본값으로 0
을 리턴합니다. 존재하지 않는 키 'apple'
에 접근할 때 KeyError
없이 0
을 자동으로 생성한 뒤 +1
이 수행된 것입니다.
3. 다양한 기본값 예시
from collections import defaultdict
# 기본값: 0 (int)
counter = defaultdict(int)
counter['a'] += 1
print(counter) # defaultdict(<class 'int'>, {'a': 1})
# 기본값: 빈 리스트
group = defaultdict(list)
group['fruit'].append('apple')
group['fruit'].append('banana')
print(group) # defaultdict(<class 'list'>, {'fruit': ['apple', 'banana']})
# 기본값: 빈 집합
unique_tags = defaultdict(set)
unique_tags['tags'].add('python')
unique_tags['tags'].add('coding')
print(unique_tags) # defaultdict(<class 'set'>, {'tags': {'python', 'coding'}})
# 기본값: 커스텀 초기값
fixed = defaultdict(lambda: 100)
print(fixed['unknown']) # 100
4. 실전 예시
1. 단어 빈도수 세기
words = ['apple', 'banana', 'apple', 'orange', 'banana']
counter = defaultdict(int)
for word in words:
counter[word] += 1
print(counter)
# defaultdict(<class 'int'>, {'apple': 2, 'banana': 2, 'orange': 1})
👉 Counter vs defaultdict
단어의 빈도수 세기는 collections.Counter()
가 더욱 특화되어있기에 통계나 순위 분석이 필요한 경우에는 Counter
를 사용하는 것이 바람직합니다. 하지만 누적 카운팅처럼 단순한 합산에는 defaultdict(int)
도 충분히 간결하게 사용 가능합니다.
2. 로그를 그룹별로 정리하기
logs = [
('2024-01-01', 'INFO'),
('2024-01-01', 'ERROR'),
('2024-01-02', 'DEBUG'),
]
grouped = defaultdict(list)
for date, level in logs:
grouped[date].append(level)
print(grouped)
# defaultdict(<class 'list'>, {'2024-01-01': ['INFO', 'ERROR'], '2024-01-02': ['DEBUG']})
3. 중복 제거된 태그 정리
entries = [
('post1', 'python'),
('post1', 'coding'),
('post1', 'python'),
]
tags = defaultdict(set)
for post, tag in entries:
tags[post].add(tag)
print(tags)
# defaultdict(<class 'set'>, {'post1': {'python', 'coding'}})
5. 주의 사항
defaultdict
는 내부적으로 기본값 생성기를 저장하므로,repr()
시 일반dict
와 다른 모양이 나올 수 있습니다.- JSON 직렬화 시 문제가 될 수 있습니다.
dict(d)
로 변환 후 처리하는 것이 안전합니다. []
접근 시에만 기본값이 생성됩니다.get()
으로 접근하면 생성되지 않습니다.
from collections import defaultdict
d = defaultdict(list)
print(d.get('missing')) # None
print(d['missing']) # []
6. 언제 쓰면 좋은가? – defaultdict
만의 결정적 장점 3가지
defaultdict
는 dict
+ 조건문
패턴을 많이 사용하는 상황에서 가독성, 유지보수성, 안전성을 동시에 높여줍니다. 특히 다음 3가지 상황에서 '아, 이건 무조건 defaultdict 쓰는 게 낫다!'고 느끼게 됩니다.
6-1. 조건 없이 카운트/누적하는 집계 코드
from collections import defaultdict
# 일반 dict
counts = {}
for item in items:
if item not in counts:
counts[item] = 0
counts[item] += 1
# defaultdict
counts = defaultdict(int)
for item in items:
counts[item] += 1
✔ 조건문이 사라지면서 코드가 간결해지고 실수 여지도 줄어듭니다.
✔ 특히 로그 분석, 단어 카운팅 등 대량 데이터 처리에 매우 적합합니다.
6-2. 리스트/집합을 누적할 때 setdefault
를 대체
from collections import defaultdict
# 일반 dict
posts = {}
for tag, post in data:
if tag not in posts:
posts[tag] = []
posts[tag].append(post)
# defaultdict
posts = defaultdict(list)
for tag, post in data:
posts[tag].append(post)
✔ setdefault()
보다 훨씬 직관적이고 반복문 안에서도 깔끔합니다.
✔ 데이터 그룹핑에 최적화된 구조입니다.
6-3. 중첩 딕셔너리 구성시 초기화를 자동화
# 일반 딕셔너리
matrix = {}
if 'x' not in matrix:
matrix['x'] = {}
matrix['x']['y'] = 10
# defaultdict 중첩
matrix = defaultdict(lambda: defaultdict(int)) # 키가 없을 때마다 내부에 defaultdict(int)가 자동 생성됨
matrix['x']['y'] += 10
✔ 중첩된 자료구조를 손쉽게 만들 수 있어 다차원 딕셔너리 작업에 매우 유리합니다.
✔ 데이터 마이닝, 파싱, 트리 구조 저장 등에 강력한 장점을 발휘합니다.
lambda: defaultdict(int)
는 내부적으로 키가 없을 때마다defaultdict(int)
를 반환하는 구조로, 자동으로 딕셔너리가 중첩 생성됩니다.
7. 정리
collections.defaultdict
는 초보자에게는 단순한 dict
확장판처럼 보일 수 있지만, 쓰면 쓸수록 코드를 더 명확하고 안전하게 만드는 구조적 도구라는 사실을 체감하게 됩니다.
KeyError
를 걱정하지 않고 딕셔너리를 쓸 수 있다.- 조건문 없이 데이터를 그룹화하고 누적할 수 있다.
- 중첩된 딕셔너리를 직관적으로 구성할 수 있다.
# defaultdict로 안정적이고 간결하게 처리하는 예시
salaries = defaultdict(int)
for dept, amount in records:
salaries[dept] += amount
한 줄의 코드로 오류 방지 + 가독성 향상 + 유지보수성까지 잡을 수 있다면,
defaultdict
는 단순한 편의 기능이 아니라 파이썬다운 사고 방식의 핵심 도구라고 할 수 있습니다.
다음에 다룰 주제는 pathlib
입니다.
파일/디렉토리 다루는 코드를 객체지향적으로 바꿔주는 모던한 방식으로, 초보부터 중급까지 모두 유용하게 활용가능합니다. os.path
에 익숙한 개발자분들은 "와우! os.path보다 훨씬 간단하네!"라는 기분을 느끼실 것입니다. 다음 편도 기대해주세요.
Add a New Comment