__str__ и __repr__ — два лица вашего объекта
Когда Python выводит объект на экран — он вызывает один из двух методов: __str__ или __repr__. Они делают похожую вещь, но для разных людей: __str__ — для пользователя, __repr__ — для разработчика. Разобравшись в разнице, вы избавитесь от загадочных строчек вроде <MyClass object at 0x7f3a...> раз и навсегда.
😕 Что происходит, если ничего не определять
Создадим простой класс и попробуем его напечатать. Многие новички сталкиваются с этим в первый же день:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
p = Point(3, 7)
print(p)
# <__main__.Point object at 0x10e4a2f50>
str(p)
# '<__main__.Point object at 0x10e4a2f50>'
🤔 Что это за строка? Здесь три части: __main__ — модуль, где определён класс; Point — имя класса; 0x10e4a2f50 — адрес объекта в памяти компьютера (в шестнадцатеричной системе). Эта информация полезна Python-интерпретатору, но абсолютно бесполезна нам. Мы не видим ни x = 3, ни y = 7.
Хорошая новость: это поведение по умолчанию, и его очень легко поменять — достаточно определить __str__ и/или __repr__.
👤 __str__ — читаемое представление для людей
Метод __str__ вызывается в трёх случаях: когда вы пишете print(obj), когда вы вызываете str(obj), и когда объект попадает в f-строку вроде f"Результат: {obj}". Смысл метода — вернуть человекочитаемую строку: красивую, понятную, удобную для отображения пользователю.
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
# Этот метод должен вернуть строку (str)
# Пишите то, что удобно ЧИТАТЬ
return f"Точка({self.x}, {self.y})"
p = Point(3, 7)
print(p)
# Точка(3, 7)
str(p)
# 'Точка(3, 7)'
# В f-строке тоже работает __str__:
message = f"Координаты: {p}"
print(message)
# Координаты: Точка(3, 7)
✅ Правило __str__: возвращайте строку, которую не стыдно показать конечному пользователю. Это может быть «Корзина: 3 товара на сумму 1 290 ₽» или «Пользователь Иван Иванов (ivan@mail.ru)» — что-то информативное и читаемое.
Важно: метод __str__ обязан вернуть именно str (строку). Если вы вернёте, например, число — Python выбросит ошибку TypeError.
🔧 __repr__ — техническое представление для разработчика
Метод __repr__ (от английского representation — представление) создаёт строку для разработчика: для отладки, логирования, изучения состояния объекта. Он вызывается функцией repr(obj).
Есть золотое правило хорошего тона в Python:
🎯 Идеальный __repr__ возвращает строку, которую можно скопировать в Python и выполнить — и получить точно такой же объект. Другими словами: eval(repr(obj)) == obj. Шаблон: "ИмяКласса(param1=значение, param2=значение)".
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
# Строка должна показывать КАК воссоздать этот объект
return f"Point(x={self.x}, y={self.y})"
p = Point(3, 7)
repr(p)
# 'Point(x=3, y=7)'
# В интерактивной консоли (REPL) объект выводится через __repr__:
# >>> p
# Point(x=3, y=7)
💡 Что такое REPL? REPL — это интерактивная консоль Python, та самая, где видите >>>. Когда вы просто вводите имя переменной и нажимаете Enter — Python показывает её через __repr__. Это сделано специально: разработчик, исследующий объект в консоли, хочет техническую информацию, а не «красивый» вывод.
⚖️ __str__ и __repr__ вместе — в чём разница
Посмотрим на реальный пример, где оба метода определены, и убедимся что они ведут себя по-разному:
class Product:
def __init__(self, name, price, discount=0):
self.name = name
self.price = price
self.discount = discount
def __str__(self):
# Для пользователя: красиво, понятно
if self.discount:
final = self.price * (1 - self.discount / 100)
return f"{self.name} — {final:.0f} ₽ (скидка {self.discount}%)"
return f"{self.name} — {self.price} ₽"
def __repr__(self):
# Для разработчика: технически точно, можно воссоздать объект
return f"Product(name={self.name!r}, price={self.price}, discount={self.discount})"
p = Product("Курс Python", 1990, discount=20)
# print() вызывает __str__:
print(p)
# Курс Python — 1592 ₽ (скидка 20%)
# repr() вызывает __repr__:
print(repr(p))
# Product(name='Курс Python', price=1990, discount=20)
# str() вызывает __str__:
print(str(p))
# Курс Python — 1592 ₽ (скидка 20%)
# В REPL (просто ввести p и Enter) вызывается __repr__:
# >>> p
# Product(name='Курс Python', price=1990, discount=20)
👤
__str__ — для пользователя
- Вызывается:
print(obj),str(obj), f-строки - Цель: читаемость, красота
- Пример: «Курс Python — 1592 ₽»
🔧
__repr__ — для разработчика
- Вызывается:
repr(obj), REPL (консоль) - Цель: точность, воспроизводимость
- Пример: «Product(name='Курс Python', price=1990, discount=20)»
Обратите внимание на {self.name!r} в __repr__: суффикс !r в f-строке — это сокращение от repr(self.name). Он автоматически оборачивает строку в кавычки. Это удобно, чтобы сразу видеть тип значения (строка или число).
🔄 Если определён только __repr__ — str() тоже работает
Это важное правило Python: если __str__ не определён, Python использует __repr__ вместо него. То есть __repr__ — это запасной вариант для __str__.
class Temperature:
def __init__(self, celsius):
self.celsius = celsius
# Только __repr__, __str__ НЕ определён
def __repr__(self):
return f"Temperature(celsius={self.celsius})"
t = Temperature(36.6)
# repr() — напрямую использует __repr__:
print(repr(t))
# Temperature(celsius=36.6)
# print() и str() — __str__ не найден → Python берёт __repr__:
print(t)
# Temperature(celsius=36.6)
print(str(t))
# Temperature(celsius=36.6)
💡 Практический вывод: если в вашем классе нет смысла разделять «красивый» и «технический» вывод — определяйте только __repr__. Этого достаточно, чтобы избавиться от <Temperature object at 0x...>. __str__ добавляйте только тогда, когда хотите разные виды вывода.
Обратная ситуация — только __str__ без __repr__ — не даёт такого удобства: repr(obj) всё равно вернёт некрасивый дефолтный вид. Поэтому если определяете один метод — это должен быть __repr__.
📋 В списках и контейнерах Python использует __repr__
Это тонкий момент, который многих удивляет. Когда вы помещаете объекты внутрь списка, словаря или другого контейнера и выводите весь контейнер — Python использует __repr__ для каждого элемента, а не __str__.
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f"Точка({self.x}, {self.y})"
def __repr__(self):
return f"Point({self.x}, {self.y})"
p1 = Point(1, 2)
p2 = Point(3, 4)
p3 = Point(5, 6)
# Выводим отдельный объект через print — работает __str__:
print(p1)
# Точка(1, 2)
# Помещаем объекты в список и выводим весь список:
points = [p1, p2, p3]
print(points)
# [Point(1, 2), Point(3, 4), Point(5, 6)]
# ^-- используется __repr__, а не __str__!
# То же самое в словаре:
d = {"a": p1, "b": p2}
print(d)
# {'a': Point(1, 2), 'b': Point(3, 4)}
# ^-- тоже __repr__
🧩 Почему так? Когда Python выводит контейнер (список, словарь, кортеж), он строит строку из всех элементов. Внутри контейнера элементы представляют через repr(), потому что это контекст «для разработчика» — вы изучаете структуру данных, вам нужна точная информация, а не пользовательский вид. Поэтому хороший __repr__ особенно важен для отладки.
Проверьте это сами: если убрать __repr__ из примера выше — список выведет [<__main__.Point object at 0x...>, ...]. Именно поэтому опытные Python-разработчики всегда определяют __repr__ в своих классах.
🏆 Пример идеального __repr__
Вот эталонный паттерн. Строка из __repr__ должна быть валидным Python-кодом, который можно выполнить и получить идентичный объект. Используйте шаблон ИмяКласса(param1=значение, param2=значение):
class BankAccount:
def __init__(self, owner, balance, currency="RUB"):
self.owner = owner
self.balance = balance
self.currency = currency
def __str__(self):
# Для пользователя: счёт Ивана, баланс 15 000 RUB
return f"Счёт {self.owner}: {self.balance:,.0f} {self.currency}"
def __repr__(self):
# Для разработчика: как воссоздать этот объект
return (
f"BankAccount("
f"owner={self.owner!r}, "
f"balance={self.balance}, "
f"currency={self.currency!r})"
)
acc = BankAccount("Иван Петров", 15000, "RUB")
# Пользовательский вывод:
print(acc)
# Счёт Иван Петров: 15 000 RUB
# Технический вывод:
print(repr(acc))
# BankAccount(owner='Иван Петров', balance=15000, currency='RUB')
# Строку из repr() можно скопировать и выполнить:
# BankAccount(owner='Иван Петров', balance=15000, currency='RUB')
# → создаст точно такой же объект!
# В списке:
accounts = [acc, BankAccount("Анна Смирнова", 42000, "USD")]
print(accounts)
# [BankAccount(owner='Иван Петров', balance=15000, currency='RUB'),
# BankAccount(owner='Анна Смирнова', balance=42000, currency='USD')]
✨ Суффикс !r: в f-строке {self.owner!r} эквивалентно {repr(self.owner)}. Для строки 'Иван Петров' это добавит кавычки: 'Иван Петров'. Используйте его для строковых атрибутов в __repr__ — тогда результат будет корректным Python-кодом.
📊 Сводная таблица: что вызывается когда
Запомните эту таблицу — она отвечает на 99% вопросов о __str__ и __repr__:
| Ситуация | Есть оба | Только __repr__ | Только __str__ | Нет обоих |
|---|---|---|---|---|
print(obj) |
__str__ |
__repr__ |
__str__ |
адрес в памяти |
str(obj) |
__str__ |
__repr__ |
__str__ |
адрес в памяти |
repr(obj) |
__repr__ |
__repr__ |
адрес в памяти | адрес в памяти |
REPL (просто obj) |
__repr__ |
__repr__ |
адрес в памяти | адрес в памяти |
[obj1, obj2] (в контейнере) |
__repr__ |
__repr__ |
адрес в памяти | адрес в памяти |
⚠️ Типичные ошибки новичков
❌ Ошибка 1: возвращать не строку
def __str__(self):
return self.balance # ❌ TypeError: balance — число, а не строка
def __str__(self):
return str(self.balance) # ✅ Правильно: явно конвертируем в строку
Оба метода __str__ и __repr__ обязаны возвращать объект типа str. Если вернёте число, список или None — получите TypeError.
❌ Ошибка 2: ждать __str__ в списке
# У класса есть __str__, но НЕТ __repr__
items = [obj1, obj2]
print(items)
# [<__main__.MyClass object at 0x...>, ...] ← используется __repr__ (дефолтный)
# Решение: добавить __repr__
Помните: внутри контейнеров всегда используется __repr__. Не удивляйтесь, если красивый __str__ не помогает при выводе списка.
❌ Ошибка 3: вызывать dunder напрямую
print(obj.__str__()) # ❌ Некрасиво, хотя и работает
print(obj.__repr__()) # ❌ Некрасиво, хотя и работает
print(str(obj)) # ✅ Правильно
print(repr(obj)) # ✅ Правильно
Dunder-методы определяете вы, а вызывает Python через встроенные функции. Прямой вызов технически работает, но считается плохим стилем.
✅ Итоги урока
print(obj)иstr(obj)вызывают__str__— метод для пользователя: читаемый, красивый вывод.repr(obj)и просто ввод объекта в REPL вызывают__repr__— метод для разработчика: точная, диагностическая информация.- Если определён только
__repr__— он используется и вместо__str__. Обратное неверно. - Внутри контейнеров (список, словарь, кортеж) Python всегда использует
__repr__для элементов. - Идеальный
__repr__: строка видаИмяКласса(param1=значение, param2=значение), которую можно выполнить как Python-код. - Если определяете только один метод — пусть это будет
__repr__. Он важнее для отладки. - Оба метода обязаны возвращать
str. Возврат числа илиNoneвызоветTypeError.
👉 В следующем уроке мы разберём __eq__ и операторы сравнения — как научить Python сравнивать ваши объекты с помощью ==, <, >. Вы увидите, почему без __eq__ два объекта с одинаковыми данными считаются «разными», и как это исправить.