파이썬 NoneType 오류 (TypeError: 'NoneType' object is not subscriptable) 원인과 해결
에러 메시지가 가리키는 줄을 아무리 들여다봐도 멀쩡하다. user["name"], 그냥 딕셔너리에서 값 하나 꺼내는 평범한 코드다. 그런데 파이썬은 "'NoneType' object is not subscriptable"이라며 여기서 멈춘다. 문법도 맞고 오타도 없다. 범인은 이 줄이 아니라, user라는 변수가 어느 순간 None이 되어 버렸다는 사실이다. 파이썬의 NoneType 에러는 거의 항상 이렇게 동작한다. 터지는 곳은 None을 "쓴" 자리지만, 진짜 원인은 그 None이 "만들어진" 자리다. 그래서 이 에러를 잘 잡는 사람은 에러 줄을 고치려 들지 않고, "이 None은 대체 어디서 왔나"를 되짚는다. 이 글은 그 역추적의 기술을 중심으로, NoneType 에러의 정체와 단골 원인, 안전하게 다루는 법을 정리한다.

미리 안심할 사실 하나. None은 파이썬에서 "값이 없음"을 뜻하는 지극히 정상적인 값이다. 문제는 None 자체가 아니라, 값이 있어야 할 자리에 None이 들어온 것을 모르고 그대로 쓴 데 있다. 그러니 이 에러는 "어디서 값이 새어 None이 됐는가"를 찾으면 깔끔하게 끝난다.
이 글의 추적 순서
🧬 None의 정체
None은 파이썬에서 "여기엔 값이 없다"를 나타내는 특별한 값이다. 타입은 NoneType이고, 프로그램 전체에 딱 하나만 존재하는 단 하나의 객체다(싱글턴). 함수가 아무것도 돌려주지 않을 때, 딕셔너리에서 없는 키를 안전하게 조회할 때, 아직 값을 안 정한 변수의 초기값으로 흔히 등장한다. 여기까지는 전부 정상이다.
문제는 None이 할 수 있는 일이 거의 없다는 데 있다. None은 "빈 상자"가 아니라 "상자가 아예 없음"에 가깝다. 그래서 리스트나 딕셔너리처럼 대괄호로 안을 꺼내려 하거나(subscript), for 문으로 하나씩 돌거나(iterate), 함수처럼 괄호를 붙여 부르면(call), 파이썬은 "None에는 그런 기능이 없다"며 TypeError를 던진다.
핵심 — 터진 곳이 아니라 온 곳을 봐라
NoneType 에러의 메시지는 "None을 이렇게 쓸 수 없다"고 말할 뿐, "왜 이게 None인지"는 알려 주지 않는다. 그래서 에러 줄을 노려보는 대신, 그 줄에 쓰인 변수가 어디서 만들어졌는지를 위로 거슬러 올라가는 것이 진단의 전부다.
🎭 세 가지 얼굴
같은 뿌리(None을 잘못 썼다)에서 나오지만, 무엇을 시도했느냐에 따라 메시지가 갈린다. 메시지를 보면 "None에 뭘 하려다 걸렸는지"를 바로 알 수 있다.
| 에러 메시지 | 시도한 것 | 예시 |
|---|---|---|
| not subscriptable | 대괄호로 안을 꺼냄 | x[0], x["key"] |
| not iterable | 순회·언패킹 | for i in x, a, b = x |
| not callable | 괄호 붙여 호출 | x() |
| has no attribute (사촌) | 점으로 속성·메서드 접근 | x.upper(), x.name |
마지막 줄은 엄밀히는 AttributeError지만, 원인은 똑같이 "x가 None"이다. 즉 대괄호를 쓰면 subscriptable, 점을 찍으면 AttributeError, 괄호를 붙이면 callable 메시지로 갈릴 뿐, 되짚어야 할 질문은 네 경우 모두 하나다. "이 x는 왜 None인가."
🔎 진짜 질문 — 이 None은 어디서 왔나
전형적인 상황을 보자. 에러는 마지막 줄에서 나지만, 진범은 그 위에 있다.
def get_user(uid):
users = {"a": {"name": "김코딩"}}
user = users.get(uid) # uid가 없으면 None을 돌려줌 ← 진범
return user["name"] # None["name"] 을 시도 ← 여기서 터짐
get_user("zzz")
# TypeError: 'NoneType' object is not subscriptable
에러 줄은 return user["name"]이지만, 문제는 한 줄 위 users.get(uid)가 없는 키에 대해 None을 돌려준 것이다. 역추적은 이렇게 한다. 에러 줄의 변수(user)를 지목하고, 그 변수가 마지막으로 대입된 곳을 찾는다. 거기서 값을 만든 표현식이 "실패하면 None을 줄 수 있는" 종류인지 본다. dict.get, re.match, 반환이 없는 함수 호출 등이 대표적이다. 확신이 안 서면 그 줄 바로 아래에 print(type(user), user)를 한 줄 찍어 보면, None이 언제부터 끼어들었는지 곧장 드러난다.
또 하나 악명 높은 함정이 있다. 리스트를 정렬하려다 결과를 None으로 만들어 버리는 경우다.
# .sort()는 리스트를 제자리에서 정렬하고 None을 반환한다
nums = [3, 1, 2]
nums = nums.sort() # nums가 None이 되어 버림 ← 진범
print(nums[0])
# TypeError: 'NoneType' object is not subscriptable
sort는 리스트를 그 자리에서 바꾸고 정작 돌려주는 값은 None이다. 그런데 그 None을 다시 nums에 담아 버려서, 멀쩡하던 리스트가 사라졌다. append, reverse, update 같은 "제자리 변경" 메서드가 모두 같은 함정을 가진다.
🕵️ None을 만드는 여섯 단골
역추적을 하다 보면 None의 출처는 대개 아래 여섯 가지 안에 있다. 이 목록을 외워 두면 "아, 이건 저 경우다" 하고 바로 좁혀진다.
1. return을 빠뜨린 함수
함수 안에서 계산만 하고 return을 안 쓰면, 그 함수는 자동으로 None을 돌려준다. result = calc(x) 인데 calc 안에 return이 없으면 result는 None이다. 가장 흔하다.
2. 제자리 변경 메서드의 반환값
.sort() · .append() · .reverse() · .update() 등은 대상을 그 자리에서 바꾸고 None을 돌려준다. 이 반환값을 변수에 담으면 원래 값이 None으로 덮인다.
3. dict.get()의 키 없음
딕셔너리 d.get("key")는 키가 없으면 예외 대신 None을 조용히 돌려준다. 편리하지만, 그 결과를 바로 인덱싱·순회하면 None 에러로 이어진다.
4. re.match · re.search의 실패
정규식이 일치하지 않으면 None을 돌려준다. re.match(pat, s).group() 처럼 바로 이어 쓰면, 일치 실패 시 None.group()이 되어 터진다.
5. 환경변수·설정값의 부재
os.environ.get("VAR")처럼 없을 수 있는 설정을 읽으면 None이 나온다. 로컬에선 값이 있어 잘 되다가, 값을 안 넣은 서버에서만 None 에러가 나 헷갈리게 만든다.
6. print() 같은 함수의 반환을 담음
x = print("hi") 처럼, 화면 출력만 하고 값을 안 돌려주는 함수의 결과를 변수에 담는 실수다. print는 None을 돌려주므로 x는 None이 된다. 입문자에게 흔하다.
🛡️ 다시 안 터지게 — 안전하게 다루기
해결의 방향은 둘이다. 하나는 애초에 None이 안 나오게 출처를 고치는 것(예: 빠뜨린 return 넣기), 다른 하나는 None일 수 있다고 인정하고 쓰기 전에 걸러 내는 것이다. 후자의 표준은 is None 검사다.
# ✓ 쓰기 전에 None인지 확인 (== 아니라 is 사용)
user = users.get(uid)
if user is not None:
return user["name"]
return "이름 없음"
# ✓ dict.get 에 기본값을 함께 준다
name = user.get("name", "익명")
# ✓ 정규식은 결과를 받아 확인 후 사용 (바다코끼리 연산자)
if (m := re.match(r"\d+", text)) is not None:
print(m.group())
None 비교에는 == 대신 반드시 is / is not를 쓴다. None은 유일한 객체라 정체성(is)으로 비교하는 것이 정확하고 빠르며, 파이썬 표준 스타일이 권장하는 방식이다. 그리고 제자리 변경 메서드는 반환값을 담지 말고, 원래 변수를 그대로 쓴다.
상황별 안전 처리
타입 힌트를 더하면 예방이 한 단계 강해진다. 값이 없을 수 있는 함수의 반환을 Optional[str](즉 str 또는 None)로 표시해 두면, mypy 같은 정적 검사기가 "이 None을 확인 없이 쓰고 있다"고 실행 전에 경고해 준다. 규모가 커질수록 효과가 크다.
💬 자주 묻는 질문
Q1에러 줄은 멀쩡한데 왜 여기서 터지나요?
그 줄은 None을 "사용"한 자리일 뿐, None이 "만들어진" 자리는 대개 위쪽입니다. 에러 줄에 쓰인 변수를 지목하고, 그 변수가 마지막으로 대입된 표현식을 찾아보세요. dict.get·re.match·반환 없는 함수처럼 실패 시 None을 주는 것이 범인인 경우가 많습니다.
Q2None 비교는 == 과 is 중 뭘 쓰나요?
is / is not를 쓰세요. None은 프로그램에 하나뿐인 객체라 정체성 비교(is)가 정확하고, 사용자 정의 객체가 == 를 이상하게 재정의했더라도 영향을 안 받습니다. 파이썬 표준 스타일(PEP 8)도 None 비교는 is로 하라고 명시합니다.
Q3subscriptable, iterable, callable은 뭐가 다른가요?
None에 무엇을 하려다 걸렸는지의 차이입니다. 대괄호로 안을 꺼내려 하면 not subscriptable, for로 돌거나 언패킹하면 not iterable, 괄호를 붙여 부르면 not callable입니다. 점을 찍어 속성에 접근하면 AttributeError가 되고요. 원인은 모두 "그 값이 None"으로 같습니다.
Q4None인지 빠르게 확인하려면요?
의심되는 줄 바로 아래에 print(type(변수), 변수)를 한 줄 찍어 실행하면, 그 변수가 언제 None이 됐는지 즉시 보입니다. 값이 None으로 찍히는 첫 지점이 곧 진범의 위치입니다. 규모가 크면 타입 힌트 + mypy로 실행 전에 잡는 편이 낫습니다.
📌 정리하며
"'NoneType' object is not subscriptable"은 None인 값을 리스트나 딕셔너리처럼 대괄호로 다루려다 난 에러다. 순회하면 not iterable, 호출하면 not callable, 점을 찍으면 AttributeError로 갈릴 뿐, 뿌리는 하나다. 값이 있어야 할 자리에 None이 들어왔다는 것. 그래서 해결의 출발점은 에러 줄이 아니라 "이 None이 어디서 왔나"를 위로 되짚는 일이다.
실무 원칙을 세 가지로 남긴다.
🔎 출처를 역추적한다. 에러 줄의 변수를 지목하고, 그것이 마지막으로 대입된 곳으로 올라가 None을 만든 표현식을 찾는다.
🛡️ 쓰기 전에 거른다. None일 수 있는 값은 is not None으로 확인하고 분기하거나, dict.get에 기본값을 준다. 비교는 == 아닌 is로.
🧰 출처를 아예 막는다. 빠뜨린 return을 넣고, 제자리 변경 메서드의 반환을 담지 않으며, Optional 타입 힌트와 정적 검사로 실행 전에 잡는다.
NoneType 에러 체크리스트
본 글은 파이썬 NoneType 관련 TypeError의 일반적 원인과 해결 방법을 정리한 자료다. 코드 수정은 영향 범위를 확인한 뒤 신중히 적용한다.
#파이썬 #Python #NoneType #TypeError #파이썬에러 #None #subscriptable #파이썬디버깅 #파이썬초보 #예외처리 #타입힌트 #코딩 #프로그래밍 #파이썬문법 #mypy
'IT' 카테고리의 다른 글
| 파이썬 키 에러 (KeyError) 원인과 딕셔너리 안전하게 다루는 법 (1) | 2026.07.07 |
|---|---|
| 파이썬 인덱스 오류 (IndexError: list index out of range) 원인과 해결 (0) | 2026.07.07 |
| 파이썬 모듈 없음 오류 (ModuleNotFoundError: No module named) 진짜 원인과 해결 (0) | 2026.07.07 |
| 파이썬 모듈 없음 오류 (ModuleNotFoundError: No module named) 진짜 원인과 해결 (0) | 2026.07.03 |
| JS 에러 3대장 완전 정리 — TypeError·ReferenceError·SyntaxError 한눈에 (0) | 2026.07.02 |
댓글