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

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

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

Реализация всех четырёх методов в классе Playlist и проверка

Соберём всё вместе в одном классе. Playlist хранит имя плейлиста и список треков — достаточно, чтобы осмысленно реализовать каждый из четырёх методов.

class Playlist:
    def __init__(self, name: str, tracks: list[str]):
        self.name = name
        self.tracks = tracks

    def __repr__(self) -> str:
        return f"Playlist(name={self.name!r}, tracks={self.tracks!r})"

    def __str__(self) -> str:
        return f"Playlist '{self.name}' ({len(self.tracks)} tracks)"

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, Playlist):
            return NotImplemented
        return self.name == other.name and self.tracks == other.tracks

    def __len__(self) -> int:
        return len(self.tracks)

В этом классе __repr__ и __eq__ используют одни и те же атрибуты (name и tracks), поэтому их поведение согласовано: объекты с одинаковыми данными имеют одинаковый repr и равны через ==. Это хорошая практика проектирования — держать repr и eq в согласии, — но Python не обеспечивает её автоматически. В другом классе одинаковый repr не обязан означать равенство, и равные объекты не обязаны иметь одинаковый repr. __str__ намеренно лаконичнее: показывает только количество треков, а не их содержимое. Порядок треков при сравнении важен, потому что self.tracks — список.

p1 = Playlist("Rock", ["Song A", "Song B"])
p2 = Playlist("Rock", ["Song A", "Song B"])
p3 = Playlist("Jazz", ["Blue Moon"])
empty = Playlist("Empty", [])

repr(p1)
# "Playlist(name='Rock', tracks=['Song A', 'Song B'])"

print(p1)
# Playlist 'Rock' (2 tracks)

f"{p1}"
# "Playlist 'Rock' (2 tracks)"

[p1, p3]
# [Playlist(name='Rock', tracks=['Song A', 'Song B']), Playlist(name='Jazz', tracks=['Blue Moon'])]

p1 == p2   # True
p1 == p3   # False
p1 == 42   # False  — благодаря NotImplemented, который мы разобрали выше

p4 = Playlist("Rock", ["Song C", "Song D"])
p1 == p4   # False
repr(p4)
# "Playlist(name='Rock', tracks=['Song C', 'Song D'])"

len(p1)     # 2
len(empty)  # 0
bool(p1)    # True
bool(empty) # False

Обратите внимание на пару p1 / p4: имя одинаковое, треки разные — __eq__ возвращает False, а __repr__ наглядно показывает разницу. Если бы __repr__ отображал только количество треков, оба плейлиста выглядели бы идентично в отладчике, хотя данные различаются. Именно поэтому здесь показываем полный список. Контейнер [p1, p3] использует __repr__ элементов — и благодаря этому в выводе видны реальные данные, а не лаконичные строки из __str__.

В следующем уроке @dataclass сгенерирует __repr__ и __eq__ автоматически; ручная реализация помогает понимать, что именно генерируется и почему.