Перейти к содержимому
Теория · Классы · урок 3 из 11

__str__, __repr__ и __eq__

В прошлом уроке мы заполняли карточки по чертежу. Теперь представьте, что карточку нужно кому-то показать или сравнить с другой. Человек сначала прочтёт, что на ней написано. Python так не умеет: экземпляр нового класса он не может ни показать по-человечески, ни сравнить по содержимому.

Давайте посмотрим на точку с координатами:

Python
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y


p = Point(1, 2)
print(p == Point(1, 2))  # False
print(p)

Здесь две неожиданности:

  • p == Point(1, 2) даёт False, хотя координаты одинаковые;
  • последняя строка печатает что-то вроде <__main__.Point object at 0x7f3a2c1b2d50>: имя класса и адрес объекта в памяти, который меняется от запуска к запуску.

По умолчанию == проверяет, тот ли это объект, а не одинаковы ли координаты. Оба поведения класс может заменить своими.

Специальные методы

Методы, имя которых начинается и заканчивается двумя подчёркиваниями, — специальные методы. Обычно их не вызывают по имени. Python вызывает их сам в определённых ситуациях, как __init__ при создании экземпляра.

Для показа объекта у класса два таких метода. Они отвечают на разные вопросы, поэтому разберём каждый.

__str__ — для людей

Метод __str__ возвращает строку для читателя программы. Её показывают print, str() и f-строки. Это как подпись на карточке крупными буквами: «точка (1, 2)».

__repr__ — для программиста

Метод __repr__ возвращает строку для программиста. Её показывает функция repr(), а ещё список, кортеж и словарь для своих элементов. Текст __repr__ обычно похож на вызов, который создаёт такой же объект: Point(1, 2).

Соберём оба метода в одном классе:

Python
class Point:
    def __init__(self, x, y):
        self.x, self.y = x, y

    def __str__(self):
        return f"({self.x}, {self.y})"

    def __repr__(self):
        return f"Point({self.x}, {self.y})"


p = Point(1, 2)
print(p)  # (1, 2)
print([p, Point(3, 4)])  # [Point(1, 2), Point(3, 4)]
print(f"p = {p}", repr(p))  # p = (1, 2) Point(1, 2)

Разберём вывод по строкам:

  1. print(p) вызывает __str__ и печатает (1, 2).
  2. print([p, Point(3, 4)]) печатает список, а список показывает каждый элемент через __repr__.
  3. f-строка берёт __str__, а repr(p) — __repr__.

Строку __init__ здесь короче записывает распаковка: self.x, self.y = x, y. Кавычки в ['a'] — тоже часть __repr__: так показывает себя строка.

Порядок методов в классе не важен. Если __str__ в классе нет, print использует __repr__. Поэтому, если пишете только один из двух, пишите __repr__.

Сравнение через __eq__

Представьте два экземпляра одной книги. Текст в них одинаковый, но на полке стоят две книги, а не одна. В Python это два разных вопроса:

  • a is b спрашивает, один ли это объект;
  • a == b спрашивает, равны ли объекты по содержимому.

Без своего метода == у класса отвечает на первый вопрос, как is: равен только сам объект. Метод __eq__(self, other) задаёт сравнение по содержимому. Python вызывает его для a == b, передавая a в self и b в other:

Python
class Point:
    def __init__(self, x, y):
        self.x, self.y = x, y

    def __eq__(self, other):
        return self.x == other.x and self.y == other.y


p = Point(1, 2)
print(p == Point(1, 2), p is Point(1, 2))  # True False
print(p != Point(3, 4))  # True

__eq__ возвращает True или False. Оператор != Python выводит из __eq__ сам. На is метод не влияет: два вызова Point(1, 2) создают разные объекты.

В other попадает любой объект справа от ==. Сравнение p == 5 здесь остановится с AttributeError, потому что у числа нет атрибута x.

Этот вопрос часто задают на собеседовании: «Чем == отличается от is?» is сравнивает объекты по тождеству, то есть проверяет, один ли это объект. == вызывает __eq__ и сравнивает по содержимому, если класс его задал.

Частая ошибка

__str__ возвращает не строку, а число:

Python
class Point:
    def __init__(self, x):
        self.x = x

    def __str__(self):
        return self.x  # ✗


print(Point(1))
TEXT
TypeError: __str__ returned non-string (type int)

print требует, чтобы __str__ и __repr__ возвращали str. Соберите строку f-строкой: return f"{self.x}".

Главное

  • __str__ даёт строку для print, str() и f-строк, а __repr__ — для программиста и для элементов списка.
  • Без __str__ функция print использует __repr__.
  • Без __eq__ оператор == сравнивает объекты как is; __eq__ задаёт сравнение по содержимому.
  • __str__ и __repr__ обязаны возвращать str.

Подпишись, чтобы пройти курс

Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.

Войти другим способом