Как работает перегрузка операторов в Python? Хочу складывать свои объекты через +
Сделал класс Vector с координатами x и y. Хочу складывать два вектора просто через v1 + v2, но получаю TypeError: unsupported operand type(s) for +. Видел упоминания про какие-то методы с двойными подчёркиваниями (__add__ и т.п.), но не понял, как их прикрутить к своему классу.
2 ответа
Перегрузка операторов в Python делается через dunder-методы (от double underscore — двойное подчёркивание). Когда ты пишешь v1 + v2, Python под капотом вызывает v1.__add__(v2). По умолчанию у твоего класса этого метода нет — отсюда TypeError. Определяешь его сам — и + начинает работать:
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other): # v1 + v2
return Vector(self.x + other.x, self.y + other.y)
def __eq__(self, other): # v1 == v2
return self.x == other.x and self.y == other.y
def __repr__(self): # как объект показывается в консоли
return f"Vector({self.x}, {self.y})"
def __len__(self): # len(v) — вернёт целое
return 2
v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1 + v2) # Vector(4, 6)
print(v1 == Vector(1, 2)) # True
Разберём по шагам, что мы подключили:
__add__— оператор+. Аналогично есть__sub__(-),__mul__(*),__truediv__(/).__eq__— сравнение==. Очень советую определять его вместе с__repr__.__lt__— «меньше» (<). Достаточно определить его, и объекты можно будет сортировать черезsorted().__repr__— строковое представление для отладки. Без него в консоли увидишь бесполезное<__main__.Vector object at 0x...>.__len__— позволяет вызыватьlen(obj)(должен вернуть неотрицательное целое).
Почему это удобно: твой класс начинает вести себя как встроенный тип — интуитивно и читаемо. Сравни v1 + v2 против v1.add(v2). Но не злоупотребляй: перегружай оператор только если его смысл очевиден (для Money, Vector сложение логично; делать + для класса «Пользователь» — путь к боли).
Важная тонкость про __eq__: как только ты его определяешь, объект по умолчанию становится нехешируемым — его нельзя положить в set или использовать ключом dict. Python специально обнуляет __hash__, чтобы не было рассинхрона между равенством и хешем.
Если объект неизменяемый (immutable) и тебе нужна хешируемость — добавь __hash__:
class Money:
def __init__(self, amount):
self.amount = amount
def __eq__(self, other):
return self.amount == other.amount
def __hash__(self):
return hash(self.amount)
И ещё: если не хочешь писать все методы сравнения руками (__lt__, __le__, __gt__...), глянь декоратор functools.total_ordering — допишешь __eq__ и __lt__, остальные он сгенерит сам.