Добавить в корзинуПозвонить
Найти в Дзене

Python перегрузка операторов

В Python "перегрузка операторов" (operator overloading) — это возможность изменять поведение стандартных операторов (таких как +, -, *, /, ==, <, [] и т. д.) для пользовательских классов. Это позволяет вашим объектам взаимодействовать с операторами интуитивно понятным способом, делая код более читаемым и выразительным. Когда вы определяете специальные методы (их еще называют "магическими" методами или "dunder" методами, от "double underscore" — двойное подчеркивание, например, __add__), Python автоматически вызывает эти методы, когда вы используете соответствующий оператор с экземплярами вашего класса. Как это работает? Каждый оператор в Python соответствует определенному магическому методу: Операция Метод Описание + __add__(self, other) Сложение — __sub__(self, other) Вычитание * __mul__(self, other) Умножение / __truediv__(self, other) Истинное деление (всегда возвращает float) // __floordiv__(self, other) Целочисленное деление % __mod__(self, other) Остаток от деления (модуль) ** __

В Python "перегрузка операторов" (operator overloading) — это возможность изменять поведение стандартных операторов (таких как +, -, *, /, ==, <, [] и т. д.) для пользовательских классов. Это позволяет вашим объектам взаимодействовать с операторами интуитивно понятным способом, делая код более читаемым и выразительным.

Когда вы определяете специальные методы (их еще называют "магическими" методами или "dunder" методами, от "double underscore" — двойное подчеркивание, например, __add__), Python автоматически вызывает эти методы, когда вы используете соответствующий оператор с экземплярами вашего класса.

Как это работает?

Каждый оператор в Python соответствует определенному магическому методу:

Операция

Метод

Описание

+

__add__(self, other)

Сложение

__sub__(self, other)

Вычитание

*

__mul__(self, other)

Умножение

/

__truediv__(self, other)

Истинное деление (всегда возвращает float)

//

__floordiv__(self, other)

Целочисленное деление

%

__mod__(self, other)

Остаток от деления (модуль)

**

__pow__(self, other[, modulo])

Возведение в степень

<<

__lshift__(self, other)

Побитовый сдвиг влево

>>

__rshift__(self, other)

Побитовый сдвиг вправо

&

__and__(self, other)

Побитовое И

`

`

__or__(self, other)

^

__xor__(self, other)

Побитовое исключающее ИЛИ (XOR)

~

__invert__(self)

Побитовое НЕ (унарный оператор)

==

__eq__(self, other)

Равенство

!=

__ne__(self, other)

Неравенство

<

__lt__(self, other)

Меньше

<=

__le__(self, other)

Меньше или равно

>

__gt__(self, other)

Больше

>=

__ge__(self, other)

Больше или равно

[index] (получение)

__getitem__(self, key)

Доступ по индексу/ключу (например, obj[key])

[index] = value (установка)

__setitem__(self, key, value)

Установка значения по индексу/ключу (например, obj[key] = value)

Del obj[index] (удаление)

__delitem__(self, key)

Удаление элемента по индексу/ключу (например, del obj[key])

Len(obj)

__len__(self)

Длина объекта

Bool(obj)

__bool__(self)

Логическое преобразование (если не определен, используется __len__)

Str(obj)

__str__(self)

Строковое представление для пользователя (читабельное)

Repr(obj)

__repr__(self)

Строковое представление для разработчика (однозначное)

Obj() (вызов объекта)

__call__(self[, args…])

Вызов экземпляра как функции

In

__contains__(self, item)

Проверка на вхождение (item in obj)

+obj (унарный плюс)

__pos__(self)

Унарный плюс

-obj (унарный минус)

__neg__(self)

Унарный минус

Пример: Класс Vector

Давайте создадим класс Vector для двухмерных векторов и перегрузим операторы сложения, умножения и сравнения.

Python

Class Vector:

def __init__(self, x, y):

self. x = x

self. y = y

def __repr__(self):

"""

Представление объекта для разработчика (для отладки)

"""

return f"Vector({self. x}, {self. y})"

def __str__(self):

"""

Представление объекта для пользователя (для печати)

"""

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

# Перегрузка оператора сложения (+)

def __add__(self, other):

if isinstance(other, Vector):

return Vector(self. x + other. x, self. y + other. y)

else:

raise TypeError("Можно складывать только Vector с Vector")

# Перегрузка оператора вычитания (-)

def __sub__(self, other):

if isinstance(other, Vector):

return Vector(self. x — other. x, self. y — other. y)

else:

raise TypeError("Можно вычитать только Vector из Vector")

# Перегрузка оператора умножения (*)

# Это может быть скалярное умножение (Vector * number)

def __mul__(self, scalar):

if isinstance(scalar, (int, float)):

return Vector(self. x * scalar, self. y * scalar)

else:

raise TypeError("Vector можно умножать только на числовой скаляр")

# Перегрузка оператора обратного умножения (*)

# Позволяет выполнять number * Vector

def __rmul__(self, scalar):

return self.__mul__(scalar) # Делегируем обычному __mul__

# Перегрузка оператора равенства (==)

def __eq__(self, other):

if isinstance(other, Vector):

return self. x == other. x and self. y == other. y

return NotImplemented # Указывает, что сравнение с другим типом не поддерживается

# Перегрузка оператора неравенства (!=)

def __ne__(self, other):

return not self.__eq__(other) # Часто можно реализовать через __eq__

# Перегрузка унарного минуса (-obj)

def __neg__(self):

return Vector(-self. x, — self. y)

# Создание экземпляров

V1 = Vector(2, 3)

V2 = Vector(5, 7)

V3 = Vector(2, 3) # Для проверки равенства

Print(f"v1: {v1}") # Вызовет __str__

Print(f"v2: {v2}") # Вызовет __str__

# Использование перегруженных операторов

V_sum = v1 + v2

Print(f"v1 + v2 = {v_sum}") # Вывод: v1 + v2 = (7, 10)

V_diff = v2 — v1

Print(f"v2 — v1 = {v_diff}") # Вывод: v2 — v1 = (3, 4)

V_scaled = v1 * 3

Print(f"v1 * 3 = {v_scaled}") # Вывод: v1 * 3 = (6, 9)

V_scaled_reverse = 4 * v2

Print(f"4 * v2 = {v_scaled_reverse}") # Вывод: 4 * v2 = (20, 28)

V_neg = — v1

Print(f"-v1 = {v_neg}") # Вывод: — v1 = (-2, -3)

Print(f"v1 == v2: {v1 == v2}") # Вывод: v1 == v2: False

Print(f"v1 == v3: {v1 == v3}") # Вывод: v1 == v3: True

Print(f"v1 != v2: {v1 != v2}") # Вывод: v1 != v2: True

# Попытка некорректной операции

Try:

v1 + 10

Except TypeError as e:

print(f"Ошибка: {e}") # Вывод: Ошибка: Можно складывать только Vector с Vector

Важные моменты и лучшие практики:

Читаемость и интуитивность: Перегружайте операторы только тогда, когда это делает код более читаемым и когда поведение оператора интуитивно понятно для вашего типа данных. Не злоупотребляйте этим. Например, умножение вектора на вектор (v1 * v2) может означать скалярное произведение, векторное произведение или поэлементное умножение — это может сбить с толку, если не договориться о конкретном значении. Возвращаемое значение: Большинство магических методов, соответствующих бинарным операторам, должны возвращать новый объект, а не изменять текущий (если это не оператор присваивания, такой как __iadd__). Это соответствует принципу неизменяемости для многих встроенных типов (например, сложение строк возвращает новую строку, а не изменяет существующую). __repr__ vs __str__:

__repr__ (представление для разработчика): Должен быть однозначным, по возможности, позволяющим воссоздать объект (Vector(2, 3)). Используется repr(). __str__ (представление для пользователя): Должен быть читаемым и удобным для человека ((2, 3)). Используется str() и print().

Обратные (reflected) операторы (__radd__, __rmul__ и т. д.): Если вы пытаетесь выполнить other + self (например, 3 + vector_obj), и other не знает, как сложить себя с вашим объектом, Python попробует вызвать __radd__ (reverse add) вашего объекта. Часто __radd__ и другие обратные операторы просто вызывают их прямые аналоги, если операция симметрична (как в примере с __rmul__). Операторы на месте (__iadd__, __isub__ и т. д.): Соответствуют операторам присваивания, таким как +=, -=. Они должны изменять объект на месте и возвращать self.

Python

# Пример __iadd__

Class MutableVector:

def __init__(self, x, y):

self. x = x

self. y = y

def __repr__(self):

return f"MutableVector({self. x}, {self. y})"

def __iadd__(self, other):

if isinstance(other, MutableVector):

self. x += other. x

self. y += other. y

return self # Важно вернуть self для операторов на месте

else:

return NotImplemented # Или вызвать TypeError

Mv1 = MutableVector(1, 1)

Mv2 = MutableVector(2, 2)

Print(f"Перед iadd: {mv1}") # Вывод: Перед iadd: MutableVector(1, 1)

Mv1 += mv2

Print(f"После iadd: {mv1}") # Вывод: После iadd: MutableVector(3, 3)

NotImplemented: При реализации операторов сравнения или бинарных операторов, если вы не можете (или не хотите) обрабатывать операцию с данным типом other, верните NotImplemented. Это позволяет Python попробовать другие варианты (например, __radd__ у other). Не вызывайте TypeError сразу, если это не гарантированно некорректная операция. Хеширование (__hash__): Если вы переопределяете __eq__, и ваш объект должен быть хешируемым (т. е. использоваться в качестве ключей словаря или элементов множества), вы также должны переопределить __hash__. Если объект изменяем, обычно не стоит его хешировать. Если вы определяете __eq__ и не определяете __hash__ (или устанавливаете __hash__ = None), объект по умолчанию становится нехешируемым.

Перегрузка операторов — это мощная функция, которая при правильном использовании может сделать ваш код более элегантным, интуитивно понятным и Pythonic.