반응형

바쁜 분은 이것만

  1. 키가 없을 수도 있으면 d.get("키", 기본값) — 없으면 기본값, 에러 없음
  2. 개수 세기: cnt[w] = cnt.get(w, 0) + 1 또는 Counter(words)
  3. KeyError가 났는데 키가 분명 있다면: print(list(d.keys()))로 공백·대소문자·숫자/문자 확인

파이썬 딕셔너리에서 없는 키를 꺼내면 이런 에러가 납니다.

Traceback (most recent call last):
  File "tb.py", line 2, in <module>
    print(scores["영희"])
          ~~~~~~^^^^^^^^
KeyError: '영희'

에러 메시지 마지막 줄의 '영희'가 없는 키 이름입니다. 해결법은 상황마다 다른데, 자주 나오는 경우를 Python 3.14에서 실행한 결과로 정리했어요.

1. [] 대신 get()

scores = {"철수": 85, "민지": 90}
scores["민지"]           # 90
scores.get("영희")       # None
scores.get("영희", 0)    # 0
"영희" in scores         # False
  • d[키]: 키가 없으면 KeyError. 반드시 있어야 하는 키에 씁니다(없으면 오히려 에러가 나야 버그를 찾기 쉬워요).
  • d.get(키, 기본값): 없으면 기본값(안 주면 None). 있어도 되고 없어도 되는 키에 씁니다.
  • 키 in d: 있는지만 확인.

주의할 점 하나. get()의 기본값은 키가 없을 때만 나옵니다. 키는 있는데 값이 None이면 None이 그대로 나와요.

d = {"a": None}
d.get("a", "기본값")     # None

2. 키가 분명히 있는데 KeyError: 공백·대소문자

row = {"name ": "철수", "Age": 20}
row["name"]
KeyError: 'name'

CSV 헤더나 엑셀에서 읽은 데이터에서 자주 나옵니다. 화면에선 같아 보여도 키 끝에 공백이 붙어 있거나 대문자가 섞여 있어요. 이럴 땐 키 목록을 그대로 찍어 보면 바로 보입니다.

print(list(row.keys()))
['name ', 'Age']

'name ' 뒤의 공백이 보이죠. 읽을 때 {k.strip().lower(): v for k, v in row.items()}처럼 키를 정리해 두면 됩니다.

3. 숫자 키가 JSON을 거치면 문자열이 된다

import json
j = json.loads(json.dumps({1: "one"}))
print(j)
j[1]
{'1': 'one'}
KeyError: 1

JSON은 키를 문자열로만 저장합니다. 파일로 저장했다가 다시 읽으면 1이 '1'이 돼서 j[1]은 KeyError, j["1"]은 됩니다.

4. 개수 세다가 KeyError

words = ["사과", "배", "사과", "감", "사과"]
cnt = {}
for w in words:
    cnt[w] += 1
KeyError: '사과'

cnt[w] += 1은 cnt[w] = cnt[w] + 1이라, 처음 나온 단어는 꺼낼 값이 없어서 에러가 납니다. 세 가지 방법으로 고칠 수 있어요.

# ① get으로 기본값 0
cnt = {}
for w in words:
    cnt[w] = cnt.get(w, 0) + 1

# ② defaultdict: 없는 키는 int() = 0으로 자동 생성
from collections import defaultdict
dd = defaultdict(int)
for w in words:
    dd[w] += 1

# ③ Counter: 세는 전용
from collections import Counter
Counter(words).most_common(2)
{'사과': 3, '배': 1, '감': 1}
{'사과': 3, '배': 1, '감': 1}
[('사과', 3), ('배', 1)]

단순히 세는 거면 Counter가 가장 짧고, most_common(n)으로 많은 순 n개도 바로 나옵니다.

5. 그룹으로 묶을 때: setdefault

groups = {}
for name, team in [("철수", "A"), ("민지", "B"), ("영희", "A")]:
    groups.setdefault(team, []).append(name)
print(groups)
{'A': ['철수', '영희'], 'B': ['민지']}

setdefault(키, 기본값)은 키가 없으면 기본값을 넣고, 있으면 기존 값을 돌려줍니다. if team not in groups: groups[team] = [] 두 줄을 한 줄로 줄인 거예요. defaultdict(list)를 써도 같습니다.

6. API 응답처럼 중첩된 딕셔너리

data = {"user": {"profile": {"city": "대구"}}}
data.get("user", {}).get("profile", {}).get("city")    # '대구'
data.get("admin", {}).get("profile", {}).get("city")   # None
data["admin"]["profile"]                               # KeyError: 'admin'

중간 단계 기본값을 빈 딕셔너리 {}로 주면 어느 단계에서 비어도 None으로 끝납니다. 다만 너무 많이 쓰면 데이터가 잘못 와도 조용히 None이 돼서 찾기 어려우니, 반드시 있어야 하는 값은 []로 꺼내는 게 낫습니다.

7. pop과 반복 중 삭제

scores.pop("없는키", None)   # None
scores.pop("없는키")         # KeyError: '없는키'

d = {"a": 1, "b": 2}
for k in d:
    if d[k] == 1:
        del d[k]
RuntimeError: dictionary changed size during iteration

pop도 기본값을 주면 KeyError가 안 납니다. 반복문 안에서 지우면 KeyError가 아니라 RuntimeError가 나는데, for k in list(d):처럼 키 목록을 복사해서 돌리거나 {k: v for k, v in d.items() if v != 1}로 새로 만드세요.

정리

상황 쓸 것
없을 수도 있는 키 d.get(k, 기본값)
있는지 확인 k in d
개수 세기 Counter(list)
그룹 묶기 d.setdefault(k, []).append(v)
지우기 d.pop(k, None)

참고: Python 공식 문서 — Built-in Types: dict (get, setdefault, pop), collections.defaultdict, collections.Counter, json 모듈

반응형

+ Recent posts