Перегрузка операторов: __add__ и __getitem__
Плюс склеивает строки, объединяет списки и складывает числа — за каждым знаком стоит магический метод. Научим вектор складываться с вектором, плейлист — отвечать на индексы и срезы, а len() — считать элементы.
Редакция Питоники
Плюс между строками склеивает их, между списками — объединяет, между числами — складывает: знак один, поведение зависит от типов. За каждым таким знаком стоит магический метод: + — это __add__, [] — __getitem__, len() — __len__. Определив их в своём классе, вы научите вектор складываться с вектором, а заказ — объединяться с заказом. Сравнение объектов вы уже умеете переопределять — теперь очередь арифметики и индексации.
__add__: свой плюс
Выражение a + b интерпретатор читает как a.__add__(b). Если метод вернул NotImplemented, Python пробует зеркальный b.__radd__(a) — reflected-операцию, — а если и там неудача, поднимает TypeError. __radd__ особенно важен для встроенной sum(): она начинает с нуля, то есть первым вызовом будет именно 0 + vector.
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Vector({self.x}, {self.y})"
def __add__(self, other):
if not isinstance(other, Vector):
return NotImplemented
return Vector(self.x + other.x, self.y + other.y)
def __radd__(self, other):
if other == 0:
return self
return self.__add__(other)
start = Vector(2, 3)
finish = Vector(10, -1)
print(start + finish)
print(sum([start, finish]))
Vector(12, 2) Vector(12, 2)
sum() стартует с аккумулятора 0: первый шаг — 0 + start. У int нет представления о векторах, поэтому int.__add__ возвращает NotImplemented, и Python обращается к зеркальному __radd__: тот видит other == 0 и возвращает сам вектор. Дальше уже работает обычный __add__. Несовместимый тип без зеркальной поддержки даёт честную ошибку:
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
if not isinstance(other, Vector):
return NotImplemented
return Vector(self.x + other.x, self.y + other.y)
try:
Vector(1, 2) + 5
except TypeError as error:
print("Ошибка:", error)
Ошибка: unsupported operand type(s) for +: 'Vector' and 'int'
__getitem__ и __len__: индексация и срезы
Метод __getitem__ обслуживает не только obj[2], но и срезы: в playlist[1:3] в метод приходит объект slice, и обычно его просто передают внутреннему списку. __len__ подключает функцию len(). Бонус: объект с __getitem__, который бросает IndexError на границе, можно перебирать циклом for даже без __iter__ — Python сам подставляет индексы с нуля.
class Playlist:
def __init__(self, tracks):
self.tracks = list(tracks)
def __len__(self):
return len(self.tracks)
def __getitem__(self, index):
return self.tracks[index]
playlist = Playlist(["рассвет", "ветер", "прибой", "север"])
print(len(playlist))
print(playlist[0])
print(playlist[-1])
print(playlist[1:3])
4 рассвет север ['ветер', 'прибой']
| Синтаксис | Магический метод | Пример |
|---|---|---|
| a + b | __add__ | vector1 + vector2 |
| a - b | __sub__ | vector1 - vector2 |
| b + a, когда слева встроенный тип | __radd__ | 0 + vector |
| obj[i], obj[a:b] | __getitem__ | playlist[1:3] |
| obj[i] = x | __setitem__ | playlist[0] = "ветер" |
| len(obj) | __len__ | len(playlist) |
Сначала предскажи ответ в голове — это главный навык программиста.
class Money:
def __init__(self, rubles):
self.rubles = rubles
def __repr__(self):
return f"Money({self.rubles})"
def __add__(self, other):
return Money(self.rubles + other.rubles)
print(Money(120) + Money(80))
class Shelf:
def __init__(self, items):
self.items = items
def __getitem__(self, index):
return self.items[index]
shelf = Shelf(["кружка", "тарелка", "стакан"])
print(shelf[-2])
class Temp:
def __init__(self, celsius):
self.celsius = celsius
def __add__(self, other):
return Temp(self.celsius + other.celsius)
def __repr__(self):
return f"{self.celsius}°C"
print(Temp(10) + Temp(15))
1. Как интерпретируется выражение a + b?
2. Что означает возвращённый из __add__ NotImplemented?
3. В какой метод приходит срез playlist[1:3]?
4. Что нужно определить, чтобы заработала функция len(obj)?
5. Зачем в __radd__ проверка other == 0?
Создайте класс Order со списком позиций items. Определите __add__, который возвращает новый Order из позиций обоих заказов (исходные не меняются), и __len__, возвращающий число позиций. Сложите заказ «хлеб, молоко» и заказ «сыр, кофе», выведите len результата и его items.
Что такое перегрузка операторов в Python?
Это определение поведения встроенных операций для своих классов: + работает через __add__, [] — через __getitem__, len() — через __len__. Язык не меняется — вы лишь сообщаете своим объектам, как реагировать на знакомый синтаксис.
Урок 12: __eq__ и __hash__Почему в __add__ нельзя возвращать False при несовместимых типах?
False — легитимный результат операции, Python прекратит поиск и вернёт его как ответ: sum() получит мусор, а TypeError не поднимется. Правильный ответ — NotImplemented: он запускает зеркальный метод и лишь затем честную ошибку.
Как работают срезы через __getitem__?
Срез playlist[1:3] приходит в __getitem__ объектом slice с атрибутами start, stop и step. Обычно его просто передают внутреннему списку: return self.tracks[index] — список сам умеет обрабатывать slice.
Что такое reflected-операции (__radd__ и друзья)?
Это методы с префиксом r: они вызываются, когда левый операнд не справился (вернул NotImplemented). Например, 0 + vector вызывает vector.__radd__(0). Проверка other == 0 в __radd__ — стандартный способ сделать свои объекты пригодными для sum().
Понравился урок? Сошлитесь на него
Скопируйте готовую ссылку в формате HTML, Markdown или чистый адрес и вставьте в статью на Habr, VC, Telegram-канал или свой блог — так о проекте узнают новые читатели.
Что читать дальше
ООП · Урок 12
Сравнение объектов: __eq__ и __hash__
По умолчанию == для своих классов работает как is: два одинаковых счёта оказываются «не равны». Определяем __eq__ по полям, добавляем __hash__ — и объекты работают в словарях и множествах.
ООП · Урок 14
Контекстные менеджеры: __enter__ и __exit__
Блок with гарантирует очистку даже при ошибке — секрет в двух магических методах, __enter__ и __exit__. Превратим свои классы в контекстные менеджеры: журнал сессии, гашение исключений и транзакция с откатом.
Похожие уроки по темам
Подобраны автоматически по пересечению тем и ключевых слов.
Flask · Урок 14
Валидация в Flask API: плохие запросы и коды 400
Четырнадцатый урок курса Flask: API из урока 13 падает на первом же кривом запросе. Добавляем валидацию — честные 400 с понятным текстом ошибки, 404 для отсутствующих записей и тесты на плохие входы через test_client.
flask 415 unsupported media type
NumPy · Урок 3
Индексация и срезы массивов NumPy: как достать любой элемент
Индексы с нуля и с конца, срезы с шагом, строки и столбцы двумерных массивов — и главный подводный камень NumPy: срез возвращает view, а не копию. С примерами, разбором ошибок и шпаргалкой.
срезы массива pythonиндексация и срезы numpy примеры
ООП · Урок 1
Классы в Python: первый объект
Класс — чертёж, объект — экземпляр по этому чертежу. Создаём первый класс, вызываем методы и убеждаемся: в Python объектом является всё, включая функции.
type python функция