
딕셔너리로 하는 일의 대부분은 결국 세 가지입니다. 값에 접근하고, 대입하고, 삭제하는 것입니다. 이 중에서 유독 코드가 지저분해지는 지점이 "키가 아직 없을 때"의 처리입니다. 투표 집계, 태그별 그룹핑, 사용자별 이벤트 누적처럼 흔한 작업마다 같은 분기가 반복됩니다.
파이썬에는 이 상황을 다루는 방법이 네 가지 있습니다. in 식으로 미리 확인하는 방법, KeyError를 잡는 방법, get 메서드를 쓰는 방법, 그리고 setdefault를 쓰는 방법입니다. 네 가지가 다 동작하기 때문에 아무거나 골라도 될 것 같지만, 값의 타입과 생성 비용에 따라 명확한 우열이 생깁니다.
counters = {}
key = 'wheat'
if key in counters: # 해시 조회가 두 번 일어납니다
count = counters[key]
else:
count = 0
counters[key] = count + 1
votes = {}
name = 'brioche'
who = 'Elmer'
try: # 예외 흐름으로 정상 로직을 표현하고 있습니다
names = votes[name]
except KeyError:
votes[name] = names = []
names.append(who)
in 방식은 키를 두 번 조회하고 분기가 세 줄로 늘어납니다. KeyError 방식은 예외를 정상적인 제어 흐름으로 쓰는 셈이라 의도가 잘 드러나지 않습니다. 둘 다 같은 패턴이 코드베이스 곳곳에 복사되기 시작하면 관리 비용이 빠르게 불어납니다.
from collections import defaultdict
counters = {}
key = 'wheat'
counters[key] = counters.get(key, 0) + 1 # 조회 한 번으로 끝납니다
print(counters) # {'wheat': 1}
votes = {}
if (names := votes.get('brioche')) is None: # 대입식으로 중복 조회를 막습니다
votes['brioche'] = names = []
names.append('Elmer')
# 값 생성이 반복되는 구조라면 defaultdict가 더 깔끔합니다
grouped = defaultdict(list)
grouped['brioche'].append('Elmer')
카운터처럼 값이 정수나 문자열 같은 기본 타입이면 get에 디폴트를 넘기는 한 줄이 가장 짧고 빠릅니다. 값을 만드는 비용이 비싸거나 생성 도중 예외가 날 수 있다면 get과 대입식(walrus operator)을 함께 써서 필요할 때만 만들면 됩니다. setdefault는 키 존재 여부와 무관하게 디폴트 값을 매번 생성하므로, 그 패턴이 자주 보인다면 defaultdict로 넘어가는 편이 낫습니다. 가독성과 성능이 동시에 개선되는 선택입니다.
in, KeyError, get, setdefault 네 가지입니다.get의 두 번째 인자는 키가 없을 때 돌려줄 디폴트 값입니다.get이 가장 짧고 빠릅니다.get과 대입식을 조합합니다.setdefault는 이름이 헷갈리고 디폴트 값을 항상 만들어 내므로 우선순위가 낮습니다.setdefault가 적합해 보이는 상황이라면 defaultdict 사용을 먼저 검토해야 합니다.로그나 이벤트 집계 코드에서 곧바로 쓰입니다. 요청 로그를 훑으며 상태 코드별 건수를 세는 작업은 stats[code] = stats.get(code, 0) + 1 한 줄이면 충분하고, 사용자별 이벤트를 리스트로 모으는 작업은 defaultdict(list)로 분기 자체를 없앨 수 있습니다.
외부 API 응답을 파싱할 때도 유용합니다. 선택적 필드가 많은 JSON에서 payload.get('meta', {}).get('locale', 'ko')처럼 체이닝하면 중첩된 if 없이 안전한 기본값을 확보할 수 있습니다. 다만 값 생성이 비싼 경우에는 get의 인자가 항상 평가된다는 점을 기억하고 대입식 쪽으로 방향을 잡아야 합니다.