본문으로 건너뛰기
개발 머꼬
개발 노트Python
hohyeon.dev28

KeyError와 IndexError가 났을 때 get이나 try 중 무엇을 쓸지 정하는 기준

  • #Debugging
  • #Engineering Note
  • #Python

문제 발생

응답 데이터를 다루는 코드에서 두 예외가 번갈아 났습니다.

user["nickname"]      # KeyError: 'nickname'
items[3]              # IndexError: list index out of range

급한 대로 전부 try/except로 감쌌더니, 정작 진짜 버그도 함께 조용히 넘어가게 됐습니다.

원인 분석

파이썬 문서의 정의는 짧고 명확합니다.

  • KeyError — "매핑(딕셔너리)의 키가 기존 키 집합에서 발견되지 않을 때" 발생합니다.
  • IndexError — "시퀀스 첨자가 범위를 벗어났을 때" 발생합니다.

둘 다 LookupError의 하위 클래스라 함께 잡을 수 있습니다.

IndexError에는 알아 둘 만한 단서가 있습니다 — 슬라이스 인덱스는 허용 범위로 조용히 잘리고, 인덱스가 정수가 아니면 IndexError가 아니라 TypeError가 납니다.

items = [1, 2]
items[5]       # IndexError
items[2:99]    # []  — 예외 없이 빈 리스트

핵심은 예외를 없애는 방법이 아니라 "없는 것이 정상인가"를 먼저 정하는 것입니다. 정상이면 기본값으로 흘려보내고, 비정상이면 예외가 나는 편이 낫습니다 — 조용히 넘기면 잘못된 데이터가 그대로 아래로 흘러갑니다.

해결 방안

  1. 없어도 되는 값은 get으로 기본값을 줍니다.
nickname = user.get("nickname", "익명")
  1. 여러 단계라면 중간이 없을 때를 함께 처리합니다.
city = (payload.get("address") or {}).get("city")
  1. 키마다 기본값을 자동으로 만들려면 defaultdictsetdefault를 씁니다. 누적 집계에서 특히 편합니다.
from collections import defaultdict
counts = defaultdict(int)
for tag in tags:
    counts[tag] += 1          # 없던 키도 0에서 시작
  1. 있어야 하는데 없다면 예외를 살립니다. 대신 어떤 키였는지 맥락을 붙여 다시 던집니다.
try:
    user_id = payload["id"]
except KeyError as error:
    raise ValueError(f"응답에 id가 없습니다: {payload}") from error
  1. 시퀀스는 길이를 먼저 확인하거나 슬라이스를 씁니다. 슬라이스는 범위를 넘어도 예외가 나지 않으므로 "앞의 N개"류 처리에 적합합니다.
first_three = items[:3]       # 개수가 부족해도 안전
  1. except Exception으로 뭉뚱그리지 않습니다. KeyError를 잡으려다 TypeError까지 삼키면 원인을 못 찾습니다. 잡을 예외를 좁게 지정합니다.

공식 문서

마지막 수정

좋아요북마크

댓글0

아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.