본문 바로가기
Python/python

파이썬 객체 출력, __str__ 메서드 이해하기

by SANCODE 2025. 6. 17.

파이썬 클래스의 출력 결과가 낯설고 이해하기 어렵게 느껴졌다면, __str__ 메서드를 아직 제대로 활용하지 않았을 가능성이 있다. 이 글에서는 __str__이 어떤 역할을 하는지, 어떻게 사용하면 객체를 더 직관적으로 출력할 수 있는지 정리해보았다.



목차


    __str__ 메서드 이해하기


    __str__ 메서드란?

    __str__은 파이썬에서 객체를 사람이 읽기 쉬운 문자열 형태로 표현하기 위해 사용하는 특별한 메서드다. 보통 print() 함수나 str() 함수를 사용할 때 자동으로 호출된다.

    class Person:
        def __init__(self, name):
            self.name = name
    
        def __str__(self):
            return f'이름: {self.name}'
    
    p = Person("철수")
    print(p)         # 출력: 이름: 철수
    print(str(p))    # 출력: 이름: 철수

    __str__ 메서드는 객체를 print()나 str()로 출력할 때 사람이 읽기 쉬운 문자열을 반환하도록 정의하는 특수 메서드다.


    기본 타입에서의 동작

    파이썬의 기본 자료형도 모두 __str__ 메서드를 가지고 있다.

    print(str(123))          # '123'
    print(str(True))         # 'True'
    print(str([1, 2, 3]))     # '[1, 2, 3]'
    • 숫자 → 문자열 숫자
    • 불린값 → 'True' 또는 'False'
    • 리스트, 딕셔너리 등 → 사람이 알아보기 쉬운 구조 문자열

    이는 내부적으로 해당 타입의 __str__ 메서드가 정의돼 있기 때문이다.


    사용자 정의 클래스에서의 동작

    사용자 정의 클래스는 기본적으로 __str__ 메서드가 정의돼 있지 않기 때문에, 출력하면 이런 식으로 보인다.

    class Product:
        def __init__(self, name):
            self.name = name
    
    item = Product("노트북")
    print(item)  # <__main__.Product object at 0x10abcf>

    보기 불편한 메모리 주소 형태의 문자열이 출력된다.

    해결 방법

    class Product:
        def __init__(self, name):
            self.name = name
    
        def __str__(self):
            return f'상품명: {self.name}'
    
    item = Product("노트북")
    print(item)  # 상품명: 노트북


    반응형


    클래스를 읽기 좋게 만들기

    객체를 출력할 때 의미 없는 메모리 주소 대신, 사람이 읽기 좋은 정보를 보여주면 훨씬 더 직관적인 코드를 만들 수 있다. 이때 사용하는 것이 바로 __str__ 메서드다.


    예제 코드 (Person 클래스)

    class Person:
        def __init__(self, name, age):
            self.name = name
            self.age = age

    __str__ 없이 출력

    p = Person("영희", 29)
    print(p)  
    # 출력: <__main__.Person object at 0x1043...>

    __str__ 추가 후 출력

    class Person:
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
        def __str__(self):
            return f'{self.name} ({self.age}세)'
    
    p = Person("영희", 29)
    print(p)  
    # 출력: 영희 (29세)

    Person 클래스는 이름과 나이를 저장하는 객체를 만들 수 있도록 설계되어 있으며, __str__ 메서드를 정의해 객체를 출력할 때 "영희 (29세)"처럼 사람이 읽기 쉬운 형식의 문자열이 보이도록 한다. print(p)를 호출하면 내부적으로 str(p)가 실행되고, 이는 곧 p.__str__()을 호출하게 되어 "영희 (29세)"라는 결과가 출력된다.


    예제 코드 (Product 클래스)

    class Product:
        def __init__(self, name, price, stock):
            self.name = name
            self.price = price
            self.stock = stock
    
        def __str__(self):
            return f'{self.name} - {self.price}원 (재고: {self.stock}개)'
    item = Product("무선 이어폰", 79000, 12)
    print(item)
    # 출력: 무선 이어폰 - 79000원 (재고: 12개)

    __init__ 메서드는 객체가 생성될 때 이름(name), 가격(price), 재고(stock) 정보를 받아 해당 속성에 저장하고, __str__ 메서드는 이 정보를 "무선 이어폰 - 79000원 (재고: 12개)"와 같은 형태로 보기 쉽게 출력할 수 있도록 문자열을 반환한다.

    item = Product("무선 이어폰", 79000, 12)와 같이 객체를 생성하고 print(item)을 호출하면 내부적으로 item.__str__()이 실행되어 "무선 이어폰 - 79000원 (재고: 12개)"라는 결과가 출력된다.


    예제 코드 (Transaction 클래스)

    class Transaction:
        def __init__(self, from_account, to_account, amount):
            self.from_account = from_account
            self.to_account = to_account
            self.amount = amount
    
        def __str__(self):
            return f'{self.from_account} → {self.to_account}: {self.amount:,}원'
    t = Transaction("계좌1", "계좌2", 1000000)
    print(t)
    # 출력: 계좌1 → 계좌2: 1,000,000원

    Transaction 클래스는 송금 출발 계좌(from_account), 도착 계좌(to_account), 금액(amount)을 저장하는 구조로 되어 있다. 객체가 생성될 때 이 세 가지 정보를 받아 각각의 속성에 저장하며, __str__ 메서드는 이 정보를 "계좌1 → 계좌2: 1,000,000원"처럼 직관적으로 보여주는 문자열을 반환한다.

    예를 들어, t = Transaction("계좌1", "계좌2", 1000000)처럼 객체를 만들고 print(t)를 호출하면, 내부적으로 t.__str__()이 실행되어 "계좌1 → 계좌2: 1,000,000원"이라는 결과가 출력된다. 여기서 :,는 숫자를 천 단위로 쉼표 구분해 가독성을 높이기 위한 포맷팅이다.



    디버깅과 로깅

    코드를 개발하다 보면 버그를 찾거나, 프로그램의 상태를 확인하기 위해 디버깅이나 로깅을 자주 하게 된다. 이때 객체가 어떤 값들을 가지고 있는지 확인하려면 __str__ 메서드의 역할이 중요하게 작용한다.


    로깅에서 __str__의 역할

    다음과 같이 로그를 출력할 때, 객체를 직접 넣으면 __str__의 반환값이 문자열로 자동 사용된다.

    import logging
    
    class User:
        def __init__(self, username, role):
            self.username = username
            self.role = role
    
        def __str__(self):
            return f'User({self.username}, {self.role})'
    
    user = User("admin", "관리자")
    logging.info(f"접속한 사용자: {user}")

    __str__을 정의하지 않았다면 로그에 의미 없는 <__main__.User object at 0x...> 같은 문자열이 출력되지만, 커스터마이징을 해두면 "접속한 사용자: User(admin, 관리자)"처럼 직관적인 메시지를 확인할 수 있다.


    클래스 내부 상태 출력

    클래스 내부 데이터를 디버깅할 때, 다음과 같은 방식으로 __str__을 작성해두면 상태 확인이 쉬워진다.

    class Order:
        def __init__(self, id, items, total_price):
            self.id = id
            self.items = items
            self.total_price = total_price
    
        def __str__(self):
            return f'Order #{self.id}: {len(self.items)}개 상품, 총 {self.total_price:,}원'
    order = Order(1023, ['마우스', '키보드', '모니터'], 195000)
    print(order)
    # 출력: Order #1023: 3개 상품, 총 195,000원

    • 리스트나 숫자 데이터를 요약해서 보여주면 로그나 디버깅 중에도 객체 상태를 한눈에 파악할 수 있다.
    • 포맷팅도 적극적으로 활용해 가독성을 높이는 것이 좋다.

    logging + __str__ = 읽기 쉬운 로그

    실제 로그 파일에 남는 출력도 더 깔끔해지고, 운영 중인 서비스에서 객체 상태를 파악할 때 큰 도움이 된다.

    logging.debug(f"[주문 확인] {order}")
    # 로그 예시: [주문 확인] Order #1023: 3개 상품, 총 195,000원

    __str__는 사람이 읽기 쉬운 형태를, __repr__는 개발자용 디버깅 정보를 담는 것이 일반적인 관례다.

    def __repr__(self):
        return f'Order(id={self.id}, items={self.items}, total_price={self.total_price})'

    요약

    • __str__을 잘 작성해두면 print나 logging 시 객체 상태를 한눈에 파악할 수 있어 디버깅과 로깅이 훨씬 수월해진다.
    • 실제 운영 환경에서도 문제를 빠르게 추적할 수 있는 강력한 도구가 된다.
    • 가독성 높은 포맷과 핵심 정보만 담는 것이 중요하다.


    마무리

    __str__ 메서드는 단순한 출력용 도구가 아니다. 객체가 어떤 의미를 갖고 있는지, 어떤 정보를 담고 있는지를 사람이 이해하기 쉽게 표현해주는 인터페이스다. 클래스를 설계할 때 __str__을 잘 정의해두면 print()나 로그 출력, 디버깅 과정에서 훨씬 직관적이고 깔끔한 결과를 얻을 수 있다.





    반응형