
Python의 TypeError를 최소 재현으로 분리하기──메시지 읽는 순서와 None의 함정
요약
Python의 TypeError를 효율적으로 디버깅하고 최소 재현(Minimum Reproduction) 상태로 분리하는 절차를 설명합니다. Traceback을 읽는 올바른 순서와 변수의 실체를 확인하는 방법, 그리고 Claude Code와 mypy를 활용한 예방책을 다룹니다.
핵심 포인트
- TypeError는 '최종 행 → 해당 행 → 변수의 실체' 순으로 분석해야 함
- 외부 데이터(CSV, JSON) 유래 값의 타입 불일치 주의
- None 객체에 대한 인덱스 접근 및 호출 패턴 경계
- 최소 재현을 통한 빠른 원인 특정 및 타입 변환 필요
TypeError: unsupported operand type(s) for +: 'int' and 'str'
——이 메시지를 보고, 어떤 변수가 str인지 즉답할 수 있습니까? TypeError는 "타입을 잘못 맞추고 있다"는 신호이지만, 어디서 잘못 맞추었는지는 traceback을 읽는 순서를 정하지 않으면 보이지 않습니다.
이 기사에서는 TypeError를 최소 재현(Minimum Reproduction) 상태로 만들어 원인을 분리하는 절차를 traceback 읽는 법, 확인 커맨드, 수정 diff와 함께 정리합니다. 마지막으로 Claude Code에게 "원인 특정만" 부탁하는 기준과, mypy로 사전에 걸러내는 방법도 덧붙입니다. 다 읽고 나면 타입 에러의 실마리를 빠르게 잡을 수 있게 됩니다.
결론: TypeError는 「최종 행 → 해당 행 → 변수의 실체」 순으로 읽는다
TypeError의 traceback을 위에서부터 순서대로 읽으면, 자신의 코드에 도달하기도 전에 표준 라이브러리 안에서 미아가 됩니다. 읽는 순서는 언제나 같습니다.
1. 최종 행의 메시지 … 어떤 타입과 어떤 타입이 맞지 않았는가
2. 자신의 코드 중 가장 아래에 있는 행 … 어떤 식(expression)에서 발생했는가
3. 그 행에 등장하는 변수의 실체 … 실제 값과 타입을 확인한다
"메시지로 타입의 조합을 파악한다 → 해당 행을 특정한다 → 변수의 내용물을 확인한다". 이 3단계를 최소 재현 상태에서 수행하는 것이 가장 빠르다는 것이 결론입니다.
1. 전형적인 사례 1: 연산자의 타입 불일치를 최소 재현하기
주제는 집계 시 자주 발생하는 "숫자인 줄 알았는데 문자열이었던" 케이스입니다. 외부 데이터(CSV나 JSON) 유래 값은 문자열로 들어오는 경우가 많아 TypeError의 온상이 됩니다.
def total_price(items):
return sum(item["price"] for item in items)
rows = [
...
$ python totals.py
(상정 예시)
Traceback (most recent call last):
File "totals.py", line 9, in <module>
...
읽는 순서대로 살펴봅니다.
- 최종 행:
+연산이int와str사이에서 실패. 즉, "숫자에 문자열을 더하려고 했다" - 자신의 코드 최하단 행:
return sum(...)행.sum은 내부적으로0(int)부터 더하기 시작하므로, 요소에str이 섞이면int + str이 됨 - 변수의 실체:
item["price"]의 타입을 확인
확인은 해당 행 바로 앞에 한 줄을 삽입하는 것이 가장 빠릅니다.
def total_price(items):
for item in items:
print(item["name"], type(item["price"])) # ← 타입을 출력
...
(상정 예시)
apple <class 'str'>
banana <class 'int'>
apple의 price가 str임을 확정했습니다. 원인이 보였으므로 수정합니다. 입력 시 int()로 변환합니다.
- def total_price(items):
- return sum(item["price"] for item in items)
+ def total_price(items):
...
$ python totals.py
(상정 예시)
200
2. 전형적인 사례 2: None에 대한 subscript/호출
또 다른 빈출 패턴은 None을 인덱스(subscript)로 접근하거나 호출해버리는 패턴입니다.
dict.get()이나 "찾지 못하면 None을 반환하는 함수"의 반환값을 그대로 사용하면 발생합니다.
def load(data):
config = data.get("config") # 키가 없으면 None
return config["host"] # None을 subscript 접근
...
(상정 예시)
Traceback (most recent call last):
File "config.py", line 5, in <module>
...
메시지의 'NoneType' object is not subscriptable은 거의 "None에 [...]를 했다"라고 바꿔 읽을 수 있습니다. is not callable이라면 "None()처럼 호출했다", Attribute 계열(AttributeError)이라면 "None.foo를 건드렸다"라고, NoneType이 나타나면 직전 단계에서 None이 섞여 들어간 지점을 의심하는 것이 정석입니다.
수정은 None의 가능성을 명시적으로 처리합니다.
def load(data):
config = data.get("config")
- return config["host"]
...```
이렇게 하면 "설정 누락"이라는 **의도한 에러**로 바뀌며, `TypeError`로 인해 프로그램이 중단되어 원인을 찾아 헤매는 수고를 덜 수 있습니다.
## 3. Claude Code에게 맡긴다면 "원인 특정만"
최소 재현 코드가 손에 있다면, Claude Code에게 전달하여 분류를 맡길 수 있습니다. 여기서의 요령은 **수정하게 하지 말고 원인 특정만 부탁하는 것**입니다. 원인이 확정되기 전에 수정을 시키면, 잘못된 원인 위에 잘못된 차분(diff)이 쌓이게 됩니다.
```text
다음 TypeError에 대해, 수정 코드는 아직 작성하지 마세요.
(1) 어떤 식에서, 어떤 타입과 어떤 타입이 맞지 않는지
(2) 그 값이 어디서 생성되었는지 (최소 재현 코드의 몇 번째 줄인지)
...
"원인 파악 → 사람이 확인 → 수정"의 2단계 구조로 가져가면, AI의 추측을 그대로 믿지 않아도 됩니다. 확인을 위한 print나 type() 사용을 제안하게 하고, 그것을 직접 실행해 본 뒤에 수정하는 흐름이 안전합니다.
mypy로 걸러내기
함정: 실행해 보고 나서야 알게 되는 TypeError는 런타임 (Runtime) 에러입니다. 즉, "그 줄이 실행될 때까지"는 알 수 없습니다. 타입 힌트 (Type Hint)를 작성하고 mypy를 적용하면, 전형적인 오류는 실행 전에 지적할 수 있습니다.
def total_price(items: list[dict[str, int]]) -> int:
return sum(item["price"] for item in items)
$ mypy totals.py
(예상 사례: price에 str을 넣는 호출 측이 있다면,
실행하기 전에 타입 불일치로 지적됨)
모든 TypeError를 정적 분석으로 방지할 수는 없습니다 (외부 입력의 타입은 실행 시점까지 불확실합니다). 그럼에도 "숫자인 줄 알았는데 문자열", "None이 섞임"과 같은 문제의 상당수는 타입 힌트 + mypy로 사전에 차단할 수 있습니다.
요약
TypeError는 최종 행의 메시지 → 내 코드의 최하단 행 → 변수의 실체 순으로 읽는다.- 전형적인 유형은 2가지: 연산자의 타입 불일치 (
int + str등)와None에 대한 subscript / 호출. - 확인은
python -i나type()삽입으로 한다. 수정은 최소 재현 코드 위에서 diff로 진행한다. - Claude Code에는 원인 특정만 부탁하고, 확인한 뒤에 수정한다 (2단계 구조).
- 런타임 에러이므로, **타입 힌트 +
mypy**로 사전에 걸러낼 수 있는 것은 걸러낸다.
도움이 되었다면 ❤️와 Zenn 팔로우로 응원해 주세요. 다음 에러 분류 콘텐츠를 쓰는 데 큰 힘이 됩니다.
Discussion

AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기