들어가며
이전 편에서는 Python 초보자가 가장 자주 만나는 SyntaxError, IndentationError, NameError 등 기본 에러들을 다뤘습니다. 이번 편에서는 한 단계 더 나아가 실행 중에 발생하는 런타임 에러를 집중적으로 살펴보겠습니다.
TypeError, ValueError, AttributeError는 코드가 문법적으로 올바르지만, 실행 과정에서 잘못된 데이터 타입, 값, 속성 접근으로 인해 발생하는 에러입니다. 이 에러들을 정확히 이해하고 해결하는 능력은 중급 Python 개발자로 가는 필수 관문입니다.
TypeError: 타입이 맞지 않을 때
기본 개념
TypeError는 연산이나 함수에 부적절한 타입의 객체가 전달될 때 발생합니다. Python은 동적 타이핑 언어지만, 내부적으로는 엄격한 타입 체크를 수행합니다.
주요 발생 상황
1. 연산자 타입 불일치
# 문자열과 숫자 더하기
result = "나이: " + 25
# TypeError: can only concatenate str (not "int") to str
# 해결법
result = "나이: " + str(25) # 명시적 타입 변환
result = f"나이: {25}" # f-string 활용 (권장)
2. 함수 인자 개수 불일치
def greet(name, age):
return f"{name}님은 {age}세입니다"
greet("홍길동") # TypeError: greet() missing 1 required positional argument: 'age'
# 해결법: 기본값 설정
def greet(name, age=20):
return f"{name}님은 {age}세입니다"
3. None 타입 연산
data = None
print(data + 10) # TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'
# 해결법: None 체크
if data is not None:
print(data + 10)
else:
print("데이터가 없습니다")
디버깅 전략
| 에러 메시지 키워드 | 원인 | 해결 방향 |
|---|---|---|
can only concatenate |
문자열 연산 타입 불일치 | str() 변환 또는 f-string |
missing N required argument |
함수 인자 부족 | 기본값 설정 또는 인자 추가 |
unsupported operand type |
연산 불가능한 타입 조합 | 타입 체크 후 변환 |
'NoneType' object |
None 값 연산 | None 체크 로직 추가 |
ValueError: 타입은 맞지만 값이 잘못됐을 때
기본 개념
ValueError는 타입은 올바르지만, 함수나 연산이 처리할 수 없는 값이 전달될 때 발생합니다. 논리적 오류에 가깝습니다.
주요 발생 상황
1. 타입 변환 실패
# 숫자로 변환할 수 없는 문자열
age = int("스물다섯")
# ValueError: invalid literal for int() with base 10: '스물다섯'
# 해결법: 예외 처리
try:
age = int(input("나이: "))
except ValueError:
print("숫자만 입력하세요")
age = 0
2. 리스트/문자열에서 없는 값 제거
fruits = ["사과", "바나나", "오렌지"]
fruits.remove("포도") # ValueError: list.remove(x): x not in list
# 해결법: 존재 여부 확인
if "포도" in fruits:
fruits.remove("포도")
else:
print("포도가 없습니다")
3. 언패킹 값 개수 불일치
coords = (10, 20, 30)
x, y = coords # ValueError: too many values to unpack (expected 2)
# 해결법 1: 모든 값 받기
x, y, z = coords
# 해결법 2: 나머지 무시
x, y, *_ = coords
실전 팁
# 사용자 입력 검증 패턴
def get_positive_number(prompt):
while True:
try:
value = int(input(prompt))
if value <= 0:
raise ValueError("양수만 입력하세요")
return value
except ValueError as e:
print(f"오류: {e}")
age = get_positive_number("나이를 입력하세요: ")
AttributeError: 존재하지 않는 속성 접근
기본 개념
AttributeError는 객체가 가지고 있지 않은 속성이나 메서드에 접근할 때 발생합니다. 오타나 잘못된 객체 사용이 주 원인입니다.
주요 발생 상황
1. 메서드 이름 오타
text = "Hello World"
print(text.Upper()) # AttributeError: 'str' object has no attribute 'Upper'
# 해결법: 정확한 메서드명 (대소문자 구분)
print(text.upper()) # 올바름
2. None 객체 메서드 호출
def find_user(name):
if name == "admin":
return {"name": "관리자", "role": "admin"}
return None
user = find_user("guest")
print(user.get("name")) # AttributeError: 'NoneType' object has no attribute 'get'
# 해결법: None 체크
if user:
print(user.get("name"))
else:
print("사용자를 찾을 수 없습니다")
3. 리스트와 딕셔너리 혼동
data = [1, 2, 3, 4, 5]
print(data.get(0)) # AttributeError: 'list' object has no attribute 'get'
# 해결법: 올바른 접근 방법
print(data[0]) # 리스트는 인덱스로 접근
# 딕셔너리라면
data_dict = {"first": 1, "second": 2}
print(data_dict.get("first")) # 딕셔너리는 get() 사용 가능
고급 디버깅 테크닉
# dir()로 사용 가능한 속성 확인
text = "Python"
print([attr for attr in dir(text) if not attr.startswith('_')])
# ['capitalize', 'casefold', 'center', 'count', ...]
# hasattr()로 안전하게 속성 확인
if hasattr(text, 'upper'):
print(text.upper())
# getattr()로 동적 속성 접근
method_name = 'lower'
result = getattr(text, method_name, lambda: "메서드 없음")()
print(result) # 'python'
실전 디버깅 워크플로우
1단계: 에러 메시지 정확히 읽기
Traceback (most recent call last):
File "main.py", line 15, in <module>
result = calculate(data)
File "main.py", line 8, in calculate
return sum(values) / len(values)
TypeError: unsupported operand type(s) for +: 'int' and 'str'
읽는 법:
– 마지막 줄: 에러 타입과 원인 (TypeError, 타입 불일치)
– 두 번째 줄: 에러 발생 위치 (main.py 8번 줄)
– 호출 스택: 에러까지의 함수 호출 경로
2단계: print() 디버깅
def process_data(items):
print(f"DEBUG: items 타입 = {type(items)}, 값 = {items}")
total = 0
for item in items:
print(f"DEBUG: item 타입 = {type(item)}, 값 = {item}")
total += item
return total
3단계: try-except로 안전장치
def safe_divide(a, b):
try:
return a / b
except TypeError:
print(f"타입 오류: {type(a)}, {type(b)}")
return None
except ValueError:
print(f"값 오류: {a}, {b}")
return None
except Exception as e:
print(f"예상치 못한 오류: {e}")
return None
실무 예방 패턴
타입 힌트 활용
from typing import Optional, List
def get_average(numbers: List[float]) -> Optional[float]:
"""숫자 리스트의 평균을 반환. 빈 리스트면 None 반환"""
if not numbers:
return None
return sum(numbers) / len(numbers)
# IDE가 타입 오류를 미리 경고
result = get_average([1, 2, "3"]) # IDE에서 경고 표시
방어적 프로그래밍
def process_user_data(user):
# 1. None 체크
if user is None:
return "사용자 정보 없음"
# 2. 타입 체크
if not isinstance(user, dict):
return "잘못된 데이터 형식"
# 3. 키 존재 확인
name = user.get("name", "익명")
age = user.get("age", 0)
# 4. 값 검증
if not isinstance(age, int) or age < 0:
age = 0
return f"{name} ({age}세)"
마무리
TypeError, ValueError, AttributeError는 Python 개발 중 가장 빈번하게 마주치는 런타임 에러입니다. 각 에러의 특성을 정확히 이해하고, 에러 메시지를 꼼꼼히 읽는 습관을 들이면 디버깅 시간을 획기적으로 줄일 수 있습니다.
핵심 정리:
- TypeError: 타입 자체가 잘못됨 → 타입 변환 또는 함수 시그니처 확인
- ValueError: 타입은 맞지만 값이 부적절 → 입력 검증 및 예외 처리
- AttributeError: 존재하지 않는 속성 접근 → dir(), hasattr() 활용
다음 프로젝트에서 이 에러들을 만나면, 당황하지 말고 이 가이드를 참고해 차근차근 해결해보세요. 에러는 여러분의 적이 아니라, 더 나은 코드를 작성하도록 안내하는 조력자입니다!
이 글이 도움이 되셨나요? ☕
Buy me a coffee
답글 남기기