
디버깅하려고 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()로 출력됩니다.!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__에 노출하지 않도록만 주의하면 됩니다.