Python 에러 메시지 완벽 해독: TypeError, ValueError, AttributeError 실전 디버깅 가이드

들어가며

이전 편에서는 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() 활용

다음 프로젝트에서 이 에러들을 만나면, 당황하지 말고 이 가이드를 참고해 차근차근 해결해보세요. 에러는 여러분의 적이 아니라, 더 나은 코드를 작성하도록 안내하는 조력자입니다!

python 완전 초보를 위한 자주 발생하는 에러 유형 및 해결방법 모음 시리즈 (2/2편)

이 글이 도움이 되셨나요? ☕

Buy me a coffee

코멘트

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다

TODAY 66 | TOTAL 66