1. Концепция: Читаемость vs Однозначность
Небольшое отступление: Если вы только начинаете знакомиться с классами, объектами и магическими методами, приглашаю вас на мой бесплатный курс ООП Python: Часть 1 на Stepik. Базу можно заложить там.
Главное концептуальное различие между этими двумя магическими методами кроется в их целевой аудитории. Простое правило: __str__ пишется для конечных пользователей, а __repr__ — для разработчиков.
__str__(informal string representation): Его главная задача — читаемость. Этот метод отвечает за то, чтобы красиво и понятно описать объект обычным человеческим языком. Вы используете его там, где информацию увидит клиент (например, при генерации отчетов или выводе сообщений в интерфейс). Он должен скрывать лишние технические детали и показывать только суть.__repr__(official string representation): Его главная задача — однозначность и отсутствие двусмысленностей. Это ваш главный инструмент для логирования и дебага. Глядя на результат__repr__, программист должен исчерпывающе понимать, что это за объект, какого он типа и в каком состоянии находится.
Золотой стандарт Python: в идеальном мире строка, которую возвращает __repr__, должна быть валидным кодом, скопировав который, можно воссоздать точно такой же объект. То есть должно выполняться негласное правило eval(repr(obj)) == obj.
Посмотрим на практике:
class User: def __init__(self, user_id, name): self.user_id = user_id self.name = name def __str__(self): # Для пользователя: лаконично и красиво return f"Пользователь {self.name}" def __repr__(self): # Для разработчика: строго, как при создании объекта return f"User(user_id={self.user_id}, name={repr(self.name)})" user = User(1, 'Alice') # Вызываем оба метода напрямую для сравнения print(str(user)) # Вывод: Пользователь Alice print(repr(user)) # Вывод: User(user_id=1, name='Alice')
В этом примере __str__ намеренно прячет технический user_id, потому что для "человеческого" вывода он, как правило, не нужен. А вот __repr__ честно вываливает всю структуру наружу, чтобы при отладке мы в консоли точно видели, с каким именно экземпляром класса работаем.
2. Точки вызова: Где какой метод срабатывает
Мало просто написать эти методы, нужно четко понимать, в какой момент интерпретатор дергает каждый из них. Python делает это автоматически в зависимости от контекста.
Когда работает __str__?
Он срабатывает всегда, когда вы явно или неявно просите Python превратить объект в обычный текст:
Вызов функции
print(obj)Явное приведение типов
str(obj)Форматирование строк: f-строки
f"{obj}"или метод"{format}".format(obj)
Когда работает __repr__?
Его юзкейсы более специфичны и направлены на отладку:
Явный вызов функции
repr(obj)Вывод переменной в интерактивной консоли REPL (когда вы просто пишете имя переменной и жмете Enter)
Внутри коллекций (Самое важное!)
Остановка на последнем пункте обязательна. Это классические грабли. Если вы положите свои объекты в список (или словарь, или кортеж) и попытаетесь распечатать саму коллекцию через print(my_list), Python вызовет __str__ для самого списка (чтобы нарисовать квадратные скобки), но для каждого элемента внутри него он вызовет **именно __repr__**, а не __str__.
Разработчики языка сделали это намеренно: в коллекциях важна структура и однозначность элементов, чтобы при дебаге большого списка вы точно видели типы данных внутри, а не просто красивые описания.
Посмотрим на практике:
Возьмем наш класс User из предыдущего примера и поместим объекты в список.
user1 = User(1, 'Alice') user2 = User(2, 'Bob') users_list = [user1, user2] # 1. Срабатывает __str__ print(user1) # Вывод: Пользователь Alice # 2. Срабатывает __str__ (внутри f-строки) print(f"В систему вошел {user1}") # Вывод: В систему вошел Пользователь Alice # 3. Срабатывает __repr__ для элементов списка! print(users_list) # Вывод: [User(user_id=1, name='Alice'), User(user_id=2, name='Bob')]
Если бы для элементов списка вызывался __str__, мы бы увидели [Пользователь Alice, Пользователь Bob]. Вроде бы красиво, но при отладке сложного кода это скроет от нас важные детали (например, user_id). Именно поэтому коллекции всегда опираются на __repr__.
3. Механика фолбэка (Кто кого подменяет)
Что произойдет, если вы реализуете только один из этих двух методов? Интерпретатор Python использует так называемый механизм фолбэка (fallback) — запасной план действий. И этот механизм работает строго в одну сторону.
Сценарий 1: Есть только __repr__
Если Python пытается получить строковое представление объекта для человека (например, через print(obj)), но метод __str__ в классе не реализован, язык **автоматически откатится к __repr__**.
Логика языка здесь проста: «Разработчик не дал мне красивую строку для пользователя, поэтому я покажу ту строгую техническую строку, которая у меня есть. Это лучше, чем ничего».
class OnlyRepr: def __repr__(self): return "OnlyRepr(состояние='техническое')" obj_repr = OnlyRepr() # Вызываем print, который ищет __str__, не находит его и использует __repr__ print(obj_repr) # Вывод: OnlyRepr(состояние='техническое')
Сценарий 2: Есть только __str__
А вот в обратную сторону это не работает. Если системе нужен __repr__ (например, вы вывели список объектов или вызвали repr()), а у вас написан только __str__, Python его проигнорирует.
Почему? Потому что __str__ по определению может быть неточным, скрывать детали и нарушать правило однозначности. Для отладки такой вывод опасен. Поэтому Python сдается и возвращает базовое представление объекта — тот самый нечитаемый адрес в памяти, унаследованный от базового класса object.
class OnlyStr: def __str__(self): return "Красивое описание объекта" obj_str = OnlyStr() # Вызываем print для объекта — работает __str__ print(obj_str) # Вывод: Красивое описание объекта # Пытаемся распечатать список (вызывается __repr__ для элементов) print([obj_str]) # Вывод: [<__main__.OnlyStr object at 0x104b8bdf0>] # (Python проигнорировал наш __str__!)
Именно незнание этого одностороннего фолбэка чаще всего приводит к ситуациям: «Я же написал метод, почему в списке все равно выводится эта кракозябра с адресом памяти?!».
4. Золотое правило на практике (Best Practice)
На основе всего, как Python вызывает эти методы и как работает односторонний фолбэк, вырисовывается очень простое и элегантное правило для повседневной разработки.
Правило №1: Всегда реализуйте __repr__
Это абсолютный мастхэв (must-have) для любого кастомного класса. Возьмите за привычку писать __repr__ сразу после __init__. Это тот необходимый минимум, который спасет кучу нервов вам и вашим коллегам при отладке, чтении логов и работе в консоли.
Поскольку функция print() умеет откатываться к __repr__, написав только этот один метод, вы убиваете сразу двух зайцев: получаете и отличный инструмент для дебага (внутри коллекций в том числе), и приемлемый вывод при обычном принте объекта. Если у вас есть время написать только один магический метод — пишите __repr__.
Правило №2: Добавляйте __str__ только по необходимости
Не нужно писать __str__ для галочки, если он будет просто дублировать __repr__. Добавляйте его только тогда, когда вам действительно нужен специфический, «человечный» вывод данных, который разительно отличается от строгой технической картины.
Например, для объекта datetime:
__repr__должен показать точную структуру:datetime.datetime(2023, 10, 25, 12, 30)— идеально для разработчика.__str__должен показать читаемую дату:2023-10-25 12:30:00— идеально для пользователя интерфейса.
Итоговый пример идеального баланса:
class Task: def __init__(self, task_id, title, is_done=False): self.task_id = task_id self.title = title self.is_done = is_done def __repr__(self): # База. Строго, понятно, легко воссоздать объект. return f"Task(task_id={self.task_id}, title={self.title!r}, is_done={self.is_done})" def __str__(self): # Опциональное улучшение для UI/CLI. status = "✅" if self.is_done else "❌" return f"[{status}] {self.title}" task = Task(42, "Написать статью на Хабр") # Логируем/дебажим: print(repr(task)) # Вывод: Task(task_id=42, title='Написать статью на Хабр', is_done=False) # Показываем пользователю в консольной утилите: print(task) # Вывод: [❌] Написать статью на Хабр
Анонсы новых статей, полезные материалы, а так же если в процессе у вас возникнут сложности, обсудить их или задать вопрос по этой статье можно в моём Telegram-сообществе. Смело заходите, если что-то пойдет не так, — постараемся разобраться вместе.