← Все вопросы

Как работает перегрузка операторов в Python? Хочу складывать свои объекты через +

Задан вчера235 просмотров2 ответа
3

Сделал класс Vector с координатами x и y. Хочу складывать два вектора просто через v1 + v2, но получаю TypeError: unsupported operand type(s) for +. Видел упоминания про какие-то методы с двойными подчёркиваниями (__add__ и т.п.), но не понял, как их прикрутить к своему классу.

2 ответа

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 сложение логично; делать + для класса «Пользователь» — путь к боли).

4

Важная тонкость про __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__, остальные он сгенерит сам.

Ваш ответ

Войдите, чтобы ответить на вопрос.
Поддержать проект