Перегрузка операторов в Python
В Python можно переопределить операторы, то есть изменить операции, которые он выполняет. Это называется перегрузкой операторов. В этом руководстве вы научитесь пользоваться этим методом.
Один и тот же оператор в Python по-разному ведет себя с разными типами. Например, оператор +
в зависимости от типа операндов может складывать 2 числа, сливать 2 списка или объединять 2 строки. Когда-нибудь задумывались, почему так происходит? Дело в перегрузке операторов в Python.
Перегрузка оператора — это возможность переопределять различные операторы в классах, то есть менять операции, которые они выполняют, в зависимости от контекста.
Давайте рассмотрим пользовательский класс Point
. Он должен моделировать точку в двумерной системе координат.
class Point:
def __init__(self, x=0, y=0):
self.x = x
self.y = y
# задаем координаты точек p1, p2
p1 = Point(1, 2)
p2 = Point(2, 3)
# пытаемся вывести сумму точек
print(p1+p2)
Вывод:
Traceback (most recent call last):
File "<string>", line 9, in <module>
print(p1+p2)
TypeError: unsupported operand type(s) for +: 'Point' and 'Point'
Ошибка. Почему?
Python не знает, как «складывать» два объекта Point
. Чтобы объяснить ему это, нужно задать поведение оператора +
с операндами этого типа.
Тут-то и понадобится перегрузка оператора. Но сначала давайте познакомимся со специальными функциями в Python. Это поможет понять, как работает перегрузка операторов.
Специальные функции
Специальные функции классов начинаются с двойного подчеркивания __
. Их еще называют магическими методами.
Это не обычные методы, которые мы определяем в классе. В коде выше мы уже использовали специальную функцию __init__()
. Она вызывается каждый раз, когда мы создаем новый объект класса Point
.
В Python куча специальных методов. Подробнее прочитать о них.
С помощью специальных методов можно сделать наш класс совместимым со встроенными функциями.
>>> p1 = Point(2,3)
>>> print(p1)
<__main__.Point object at 0x00000000031F8CC0>
Мы хотим, чтобы функция print() выводила на экран координаты объекта Point. Сейчас print(
) печатает что-то не то. Мы можем определить метод __str__()
в нашем классе, который будет контролировать правильный вывод объекта. Давайте посмотрим, как это сделать.
class Point:
def __init__(self, x = 0, y = 0):
self.x =
self.y = y
def __str__(self):
return "({0},{1})".format(self.x,self.y)
Теперь попробуем еще раз.
class Point:
def __init__(self, x=0, y=0):
self.x = x
self.y = y
def __str__(self):
return "({0}, {1})".format(self.x, self.y)
# задаем координаты точки p1
p1 = Point(2, 3)
# выводим точку — ее координаты
print(p1)
Вывод:
(2, 3)
Так-то лучше. Но оказывается, когда мы используем встроенные функции str()
или format()
, вызывается этот же метод.
>>> str(p1)
'(2,3)'
>>> format(p1)
'(2,3)'
Теперь, когда вы используете str(p1)
или format(p1)
, Python вызывает специальный метод p1.__str__()
.
Давайте вернемся к перегрузке операторов.
Перегрузка оператора +
Чтобы перегрузить оператор +
, нам нужно реализовать специальный метод __add__()
в нашем классе. Внутри него мы можем делать все, что захотим. Но разумнее будет вернуть объект Point
в виде координатной суммы. Так и сделаем.
class Point:
def __init__(self, x=0, y=0):
self.x = x
self.y = y
def __str__(self):
return "({0},{1})".format(self.x, self.y)
def __add__(self, other):
x = self.x + other.x
y = self.y + other.y
return Point(x, y)
Теперь еще раз попробуем операцию сложения.
class Point:
def __init__(self, x=0, y=0):
self.x = x
self.y = y
def __str__(self):
return "({0},{1})".format(self.x, self.y)
def __add__(self, other):
x = self.x + other.x
y = self.y + other.y
return Point(x, y)
# задаем координаты точек p1, p2
p1 = Point(1, 2)
p2 = Point(2, 3)
print(p1+p2)
Вывод:
(3,5)
Когда мы используем p1 + p2
, Python вызывает p1.__add__(p2)
, а тот, в свою очередь, вызывает Point.__add__(p1,p2)
. После этого операция сложения выполняется указанным нами способом.
Точно так же мы можем перегрузить и другие операторы. Вот, какие для этого понадобятся специальные функции:
Оператор |
Выражение |
Специальная функция |
Сложение |
p1 + p2 |
|
Вычитание |
p1 - p2 |
|
Умножение |
p1 * p2 |
|
Возведение в степень |
p1 ** p2 |
|
Деление |
p1 / p2 |
|
Целочисленное деление |
p1 // p2 |
|
Взятие остатка |
p1 % p2 |
|
Побитовый сдвиг влево |
p1 << p2 |
|
Побитовый сдвиг вправо |
p1 >> p2 |
|
Побитовое И (AND) |
p1 & p2 |
|
Побитовое ИЛИ (OR) |
p1 | p2 |
|
Побитовое исключающее ИЛИ (XOR) |
p1 ^ p2 |
|
Побитовое НЕ (NOT) |
~p1 |
|
Перегрузка операторов сравнения
Python не ограничивается перегрузкой арифметических операторов. Перегружать можно и операторы сравнения.
Допустим, нам надо реализовать оператор «меньше чем» <
в классе Point
.
Мы будем сравнивать, какая из точек дальше от начала координат. Реализовать это можно вот так.
# Перегрузка оператора «меньше чем»
class Point:
def __init__(self, x=0, y=0):
self.x = x
self.y = y
def __str__(self):
return "({0},{1})".format(self.x, self.y)
def __lt__(self, other):
self_mag = (self.x ** 2) + (self.y ** 2)
other_mag = (other.x ** 2) + (other.y ** 2)
return self_mag < other_mag
# задаем значения точек p1, p2, p3
p1 = Point(1,1)
p2 = Point(-2,-3)
p3 = Point(1,-1)
# используем «меньше чем»
print(p1<p2)
print(p2<p3)
print(p1<p3)
Вывод:
True
False
False
Другие операторы сравнения тоже можно перегрузить. Вот какие специальные функции для этого нужно будет реализовать:
Оператор |
Выражение |
Специальная функция |
Меньше чем |
p1 < p2 |
|
Меньше или равно |
p1 <= p2 |
|
Равно |
p1 == p2 |
|
Не равно |
p1 != p2 |
|
Больше чем |
p1 > p2 |
|
Больше или равно |
p1 >= p2 |
|