본문 바로가기
IT

파이썬 키 에러 (KeyError) 원인과 딕셔너리 안전하게 다루는 법

by 샤나엘 2026. 7. 7.
반응형

파이썬 키 에러 (KeyError) 원인과 딕셔너리 안전하게 다루는 법

딕셔너리에서 값 하나 꺼내려다 "KeyError: 'email'"을 만나면 당황스럽다. user["name"]은 잘 되는데 user["email"]은 왜 터질까. 답은 단순하다. 그 딕셔너리에 'email'이라는 키가 아예 없기 때문이다. 파이썬 딕셔너리는 대괄호로 값을 꺼낼 때 "정확히 그 키가 있는가"만 본다. 없으면 대충 넘어가는 법 없이 곧바로 KeyError를 던진다. 그래서 이 에러의 진짜 해법은 "왜 없는지"를 파악하고, 없을 수도 있는 키를 애초에 안전하게 꺼내는 습관을 들이는 것이다. 이 글은 KeyError가 나는 이유와 단골 원인, 그리고 get·in·try·setdefault·defaultdict로 이어지는 딕셔너리 안전 조회 도구상자를 정리한다.

 

파이썬 키 에러 (KeyError)

 

미리 안심할 사실 하나. KeyError는 로직이 꼬여서가 아니라 "이 키가 항상 있다고 가정했는데 아니었다"에서 거의 다 나온다. 그래서 원인만 알면 대괄호 대신 안전한 조회 방법으로 바꾸는 것으로 깔끔하게 끝난다.

이 글의 순서

 

🔑KeyError는 왜 나나 — 딕셔너리는 정확한 키만 본다
🕳️키가 없어지는 단골 다섯 가지
🧰안전 조회 도구상자 — get·in·try·setdefault·defaultdict
🧭언제 무엇을 쓰나
👥KeyError vs 형제 에러 구분

🔑 KeyError는 왜 나나

딕셔너리는 "키로 값을 찾는" 자료구조다. user["name"]은 "name이라는 키에 연결된 값을 다오"라는 뜻이다. 그런데 대괄호 조회는 그 키가 반드시 있다고 전제한다. 없으면 빈 값을 주거나 넘어가는 게 아니라, "그런 키는 없다"며 KeyError를 던지고 그 자리에서 멈춘다.

여기서 중요한 건 파이썬이 키를 "정확히" 본다는 점이다. 사람 눈에는 비슷해 보여도 "name"과 "Name"은 다른 키이고, 문자열 "1"과 숫자 1도 다른 키다. 앞뒤에 공백이 붙은 " name"도 완전히 다른 키다. 그래서 KeyError를 만나면 "이 키가 정말 있는가, 철자·대소문자·타입·공백까지 똑같은가"를 먼저 확인해야 한다.

한 줄 요약

KeyError는 "그 키가 딕셔너리에 없다"는 신고다. 대괄호 조회(d[key])는 키가 있다고 전제하므로, 없을 수도 있는 키라면 대괄호 대신 안전한 조회(get·in·try)를 써야 한다. 철자·대소문자·타입·공백이 정확히 일치하는지도 함께 확인한다.

가장 흔한 함정은 값을 누적할 때다. 아래처럼 비어 있는 딕셔너리에 곧바로 더하려 하면, 그 키가 아직 없어서 첫 번째 등장부터 터진다.

user = {"name": "김코딩", "age": 30}
print(user["email"])        # 'email' 키가 없다
# KeyError: 'email'

counts = {}
for w in ["a", "b", "a"]:
    counts[w] += 1          # 첫 등장 시 counts[w]가 없어 터짐
# KeyError: 'a'

🕳️ 키가 없어지는 단골 다섯 가지

역추적을 하다 보면 "왜 이 키가 없나"의 이유는 대개 아래 다섯 안에 있다.

원인 설명
키 오타 "emial"처럼 철자를 틀리거나 키 이름을 착각
타입 불일치 키는 숫자 1인데 문자열 "1"로 조회 (JSON에서 흔함)
대소문자·공백 "Name" vs "name", " id"처럼 앞뒤 공백이 섞임
응답에 키가 없음 API·JSON에서 선택 항목이라 그 키가 빠져 옴
아직 안 채운 키 누적·카운팅에서 초기화 없이 곧바로 더함

특히 세 번째와 네 번째가 실무에서 자주 문다. 외부에서 들어온 데이터는 키 이름의 대소문자나 공백이 내 예상과 다를 수 있고, API 응답은 상황에 따라 어떤 키를 아예 빼고 줄 수 있다. 그래서 "내가 만든 딕셔너리"가 아니라 "밖에서 받은 딕셔너리"일수록 대괄호 조회는 위험하다.


🧰 안전 조회 도구상자

KeyError를 없애는 핵심은 대괄호 대신 상황에 맞는 안전한 조회를 쓰는 것이다. 다섯 가지 도구를 용도별로 정리한다.

# 1) get — 없으면 기본값을 준다 (딕셔너리는 안 바뀜)
email = user.get("email", "없음")   # 키 없으면 "없음"

# 2) in — 있는지 먼저 확인하고 분기
if "email" in user:
    print(user["email"])

# 3) try / except — 대개 있고 가끔 없을 때
try:
    v = user["email"]
except KeyError:
    v = "없음"

get은 가장 자주 쓰는 무기다. 없을 때 돌려줄 기본값을 함께 주면(user.get("email", "없음")) KeyError 없이 안전하게 값을 받는다. 다만 기본값을 생략하면 없을 때 None이 나오는데, 그 None을 그대로 인덱싱·순회하면 이번엔 NoneType 에러로 번질 수 있으니 웬만하면 의미 있는 기본값을 주는 게 좋다. 값을 누적하거나 그룹으로 묶을 때는 아래 두 도구가 훨씬 편하다.

# 4) setdefault — 없으면 넣고, 그 값을 돌려준다 (묶기에 유용)
groups = {}
for name, team in members:
    groups.setdefault(team, []).append(name)  # 팀별로 이름 묶기

# 5) defaultdict — 없는 키를 자동으로 만들어 준다 (카운팅 최강)
from collections import defaultdict
counts = defaultdict(int)          # 없는 키는 0에서 시작
for w in words:
    counts[w] += 1              # 이제 안 터진다

앞에서 터졌던 카운팅 코드는 defaultdict(int) 하나로 깔끔히 해결된다. 없는 키를 조회하는 순간 자동으로 0을 만들어 주기 때문이다. 순수 개수 세기라면 collections.Counter를 쓰면 한 줄로 더 간단해진다. 리스트로 묶는 그룹핑은 defaultdict(list)나 setdefault(key, [])가 제격이다.


🧭 언제 무엇을 쓰나

도구가 많아 헷갈린다면 상황으로 고르면 된다.

상황별 선택

 

get그냥 읽되 없으면 기본값이 필요할 때. 가장 흔한 선택.
in있고 없고에 따라 다르게 처리(분기)해야 할 때.
try/except키가 거의 항상 있고, 없는 건 예외 상황일 때.
setdefault키별로 리스트에 값을 모아 묶을 때.
defaultdict개수 세기·그룹핑을 반복적으로 할 때 (초기화 걱정 없음).

한 가지 구분만 기억하면 좋다. get은 딕셔너리를 바꾸지 않고 값만 돌려주지만, setdefault는 키가 없으면 그 자리에서 딕셔너리에 새로 넣는다. 그래서 "읽기만" 하려면 get, "없으면 만들면서 읽기"라면 setdefault다.


👥 KeyError vs 형제 에러

비슷한 조회 에러들과 헷갈리지 않게 한 번에 정리한다. 무엇을 어떻게 조회했느냐에 따라 에러 종류가 갈린다.

에러 언제 예시
KeyError 딕셔너리에 그 키가 없음 d["없는키"]
IndexError 리스트 인덱스가 범위 밖 lst[999]
AttributeError 그 속성·메서드가 없음 obj.없는속성
TypeError (NoneType) None을 조회·호출 None["x"]

핵심은 대상이 무엇이냐다. 대괄호로 딕셔너리를 조회했는데 키가 없으면 KeyError, 리스트를 조회했는데 인덱스가 범위를 벗어나면 IndexError다. 점(.)으로 속성에 접근했는데 없으면 AttributeError이고, 애초에 그 값이 None이면 첨자로 꺼내거나 호출할 땐 TypeError, 점으로 속성에 접근할 땐 AttributeError가 난다(둘 다 메시지가 'NoneType'을 지목한다). 메시지의 이름만 봐도 "무엇을 잘못 조회했는지"가 바로 보인다.


💬 자주 묻는 질문

Q1get과 대괄호 조회는 언제 갈라 쓰나요?

그 키가 반드시 있어야 하는 값이면 대괄호(d[key])를 써서, 없을 때 일찍 KeyError로 알아채는 게 낫습니다. 반대로 없을 수도 있는 선택 항목이라면 get에 기본값을 줘서 안전하게 받으세요. "없으면 곧 문제"인지 "없어도 괜찮은지"로 판단하면 됩니다.

Q2키는 분명히 있는데 KeyError가 나요.

타입이나 공백·대소문자가 다를 가능성이 큽니다. JSON에서 온 키는 숫자처럼 보여도 문자열("1")인 경우가 많고, 앞뒤 공백이나 대소문자가 예상과 다를 수 있습니다. list(d.keys())로 실제 키 목록을 찍어, 내가 쓰는 키와 철자·타입·공백까지 정확히 같은지 비교해 보세요.

Q3카운팅할 때마다 초기화하는 게 번거로워요.

collections.defaultdict(int)이나 collections.Counter를 쓰면 초기화가 필요 없습니다. defaultdict는 없는 키를 자동으로 0에서 시작해 주고, Counter는 아예 개수 세기에 특화돼 있어 한 줄로 끝납니다.

Q4del이나 pop에서도 KeyError가 나나요?

네. del d[key]나 d.pop(key)도 그 키가 없으면 KeyError를 냅니다. 안전하게 지우려면 d.pop(key, None)처럼 기본값을 주면 없을 때도 조용히 넘어갑니다. 참고로 집합(set)의 remove도 없는 원소를 지우려 하면 KeyError가 나며, 이땐 discard를 쓰면 됩니다.


📌 정리하며

KeyError는 딕셔너리에 그 키가 없을 때 나는 에러다. 대괄호 조회(d[key])가 "키가 반드시 있다"고 전제하기 때문에, 없을 수도 있는 키를 그대로 꺼내면 그 자리에서 멈춘다. 그래서 해결의 출발점은 "이 키가 정말 있는가, 철자·타입·공백까지 정확한가"를 확인하고, 없을 수 있는 키는 안전한 조회로 바꾸는 것이다.

 

실무 원칙을 세 가지로 남긴다.

🔑 키를 정확히 본다. name과 Name, 숫자 1과 문자열 "1", 공백 섞인 키는 서로 다르다. 실제 키 목록을 찍어 대조한다.

🧰 대괄호 대신 안전 조회를 쓴다. 읽기는 get, 분기는 in, 예외 상황은 try/except로 감싼다.

🔁 누적·그룹핑은 전용 도구로. 카운팅은 defaultdict(int)나 Counter, 묶기는 defaultdict(list)나 setdefault를 쓰면 KeyError가 아예 사라진다.

키 에러 체크리스트

 

01list(d.keys())로 실제 키를 찍어 철자·타입·공백 대조.
02없을 수도 있는 값은 d.get(key, 기본값)으로 조회.
03있고 없고로 분기해야 하면 key in dict 로 확인.
04카운팅·그룹핑은 defaultdict·Counter·setdefault로.
05del·pop은 pop(key, None)으로, set은 discard로 안전 삭제.

본 글은 파이썬 KeyError의 일반적 원인과 해결 방법을 정리한 자료다. 코드 수정은 영향 범위를 확인한 뒤 신중히 적용한다.

 

#파이썬 #Python #KeyError #dict #딕셔너리 #파이썬에러 #파이썬딕셔너리 #get #defaultdict #파이썬디버깅 #파이썬초보 #예외처리 #코딩 #프로그래밍 #자료구조

반응형

댓글