디버깅의 핵심 repr(obj)를 활용하세요

Tasker_Jang·2026년 4월 19일
post-thumbnail

🤔 이게 왜 문제인가

디버깅하려고 print(some_value)를 찍었는데, 숫자 5와 문자열 "5"가 똑같이 5로 출력돼서 타입 때문에 벌어진 버그를 놓친 경험, 한 번쯤 있으실 거예요. 아니면 내가 만든 클래스를 print했더니 <__main__.User object at 0x7f...> 같은 우주 주소만 나와서 당황했거나요.

이건 파이썬이 객체를 문자열로 바꾸는 두 가지 방식을 가지고 있기 때문입니다. 하나는 str() — 사람이 읽기 좋은 형태, 다른 하나는 repr() — 개발자가 디버깅하기 좋은 형태. 이 둘의 차이를 알면 디버깅 속도가 확 올라가고, 내 클래스도 더 친절하게 만들 수 있습니다.

💣 흔한 실수

# print는 내부적으로 str()을 호출 → 타입 정보가 사라짐
print(5)       # 5
print('5')     # 5 — 어라, 똑같이 나오네?
print([1, '2', 3])  # [1, '2', 3] — 리스트 안에서는 repr이 쓰여서 다행히 구분됨


# 내 클래스를 그냥 print하면 쓸모없는 출력
class User:
    def __init__(self, name, age):
        self.name = name
        self.age = age

u = User("지훈", 30)
print(u)  # <__main__.User object at 0x7f8b1c0e3a90>  😵

숫자 5와 문자열 '5'가 겉보기엔 똑같이 찍혀서 타입 버그의 온상이 됩니다. 내 클래스도 print해봐야 메모리 주소만 보여서 안의 값이 뭔지 알 수가 없어요.

✅ 파이썬다운 방법

디버깅할 때는 repr()을 쓰거나 f-string의 !r 플래그를 활용하세요. 내 클래스에는 __repr__을 직접 정의해주는 게 기본입니다.

# repr은 타입을 구분해준다
print(repr(5))     # 5
print(repr('5'))   # '5' — 따옴표가 붙어서 문자열임이 드러남

# f-string에서 !r 플래그로 간편하게
x = '5'
print(f"{x!r}")    # '5'
print(f"{x=}")     # x='5'  ← !r이 기본 적용됨, 디버깅 최강


# 내 클래스에 __repr__과 __str__ 직접 정의
class User:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __repr__(self):
        # 개발자용: 객체를 재구성할 수 있을 정도로 상세하게
        return f"User(name={self.name!r}, age={self.age!r})"

    def __str__(self):
        # 사람용: 읽기 좋게
        return f"{self.name}({self.age}세)"


u = User("지훈", 30)
print(u)          # 지훈(30세)              ← __str__
print(repr(u))    # User(name='지훈', age=30) ← __repr__
print(f"{u!r}")   # User(name='지훈', age=30)
[u]               # [User(name='지훈', age=30)] ← 컨테이너 안에선 항상 __repr__

핵심 규칙 세 가지입니다. 첫째, str()은 사람을 위한 표현, repr()개발자를 위한 표현. 둘째, 리스트·딕셔너리 같은 컨테이너는 내부 원소를 출력할 때 항상 repr()을 사용합니다. 그래서 [1, '2', 3]에서 '2'에 따옴표가 붙는 거예요. 셋째, __str__을 따로 정의하지 않으면 print()__repr__로 폴백합니다. 그러니 __repr__만 잘 정의해도 절반은 성공이에요.

한 가지 관례가 있는데, __repr__이 반환하는 문자열은 그 값을 다시 만들 수 있는 파이썬 식처럼 쓰는 게 이상적입니다. User(name='지훈', age=30)처럼요. 가능하면 eval()해서 원본이 복원될 정도로 상세하게.

📎 기억할 것

  • str()은 사람이 읽기 위한 것, repr()은 개발자가 디버깅하기 위한 것입니다.
  • print()는 기본적으로 str()을 호출하지만, 리스트·딕셔너리 내부 원소는 repr()로 출력됩니다.
  • f-string에서 !r을 붙이면 repr 형태로, f"{x=}"는 변수명+repr 자동 출력이라 디버깅에 최고입니다.
  • 내 클래스를 만들 때는 __repr__은 거의 필수, __str__은 사용자에게 직접 노출할 때만 추가로 정의하세요.
  • __repr__은 이상적으로 "그 값을 재구성할 수 있는 파이썬 식"처럼 생겨야 합니다.

🛠 실무에서 어디 쓸까

도메인 클래스를 만들 때마다 __repr__을 정의해두는 습관이 생산성에 큰 차이를 만듭니다. 예를 들어 주문, 결제, 사용자 같은 핵심 엔티티에 __repr__이 없으면 print(order) 한 줄로 디버깅하려 할 때마다 객체 내부를 까봐야 해요. 반대로 __repr__을 잘 정의해두면 logger.debug(f"처리 중: {order!r}") 같은 로그 한 줄이 진짜 쓸 만한 정보가 됩니다.

참고로 dataclasses.dataclass나 Pydantic 모델, SQLAlchemy의 일부 설정은 __repr__을 자동으로 생성해줍니다. 그래서 요즘 실무에서는 @dataclass로 만들고 거기서 나오는 기본 __repr__에 만족하다가, 필요할 때만 오버라이드하는 패턴이 흔해요. 커스텀할 때는 민감 정보(비밀번호, API 키 등)를 __repr__에 노출하지 않도록만 주의하면 됩니다.

profile
ML Engineer 🧠 | AI 모델 개발과 최적화 경험을 기록하며 성장하는 개발자 🚀 The light that burns twice as bright burns half as long ✨

0개의 댓글