Объектно-ориентированное программирование

Специальные методы: __repr__, __str__, __eq__, __len__

Содержание курса

Правила диспетчеризации: когда Python вызывает repr и str

Разобравшись с тем, для кого предназначен каждый метод, разберём точные правила: в каком контексте Python выбирает тот или иной.

Таблица вызововprint(obj) и str(obj) → вызывают __str__; если его нет — используется __repr__

  • repr(obj) → вызывает только __repr__; __str__ здесь не используется
  • f'{obj}' → вызывает __format__(obj, '') с пустой спецификацией формата; стандартная реализация __format__ делегирует __str__, поэтому для большинства классов результат совпадает с str(obj). Но если класс переопределяет __format__, поведение f-строки может отличаться от str() — это не синонимы
  • Интерактивная сессия (вводишь obj и нажимаешь Enter) → вызывает __repr__
  • Объект внутри контейнера ([obj], {obj}, (obj,)) → каждый элемент выводится через __repr__

Последний пункт часто удивляет. Если у тебя список объектов и ты делаешь print(my_list), Python выведет не __str__ каждого элемента, а именно __repr__. Это сделано намеренно: внутри контейнера важна диагностическая информация.

Отдельно про f-строки и format

Вот конкретный пример, где f'{obj}' и str(obj) дают разный результат:

class Temperature:
    def __init__(self, celsius):
        self.celsius = celsius

    def __str__(self):
        return f"{self.celsius}°C"

    def __format__(self, spec):
        if spec == 'f':
            return f"{self.celsius:.1f}°C"
        return f"[{self.celsius}°C]"  # переопределяем даже пустую спецификацию

t = Temperature(20)
str(t)    # '20°C'   — вызывает __str__
f'{t}'    # '[20°C]' — вызывает __format__ с пустой строкой
f'{t:f}'  # '20.0°C' — вызывает __format__ с 'f'

Для учебного Playlist мы __format__ не переопределяем, поэтому f'{p}' и str(p) дадут одинаковый результат. Но на продвинутом уровне важно понимать, что это разные точки входа.

Что происходит, если одного метода нет

Если определён только __repr__, стандартные print() и str() используют его как запасной вариант; интерактивная сессия и контейнеры тоже показывают __repr__.

Если определён только __str__, print() и str() используют его, но repr(obj) и интерактивная сессия всё равно выдают дефолтный <...>-вывод.

class OnlyStr:
    def __str__(self):
        return "я понятен пользователю"

obj = OnlyStr()
print(obj)   # я понятен пользователю
repr(obj)    # '<__main__.OnlyStr object at 0x...>'
class OnlyRepr:
    def __repr__(self):
        return "OnlyRepr()"

obj = OnlyRepr()
print(obj)   # OnlyRepr()
repr(obj)    # 'OnlyRepr()'

Если реализуешь только один из двух методов, обычно полезнее начать с информативного __repr__.