Перегрузка операторов в 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.__add__(p2)

Вычитание

p1 - p2

p1.__sub__(p2)

Умножение

p1 * p2

p1.__mul__(p2)

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

p1 ** p2

p1.__pow__(p2)

Деление

p1 / p2

p1.__truediv__(p2)

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

p1 // p2

p1.__floordiv__(p2)

Взятие остатка

p1 % p2

p1.__mod__(p2)

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

p1 << p2

p1.__lshift__(p2)

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

p1 >> p2

p1.__rshift__(p2)

Побитовое И (AND)

p1 & p2

p1.__and__(p2)

Побитовое ИЛИ (OR)

p1 | p2

p1.__or__(p2)

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

p1 ^ p2

p1.__xor__(p2)

Побитовое НЕ (NOT)

~p1

p1.__invert__()

Перегрузка операторов сравнения

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.__lt__(p2)

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

p1 <= p2

p1.__le__(p2)

Равно

p1 == p2

p1.__eq__(p2)

Не равно

p1 != p2

p1.__ne__(p2)

Больше чем

p1 > p2

p1.__gt__(p2)

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

p1 >= p2

p1.__ge__(p2)

codechick

СodeСhick.io - простой и эффективный способ изучения программирования.

2024 ©