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

Перегрузка операторов в Python – это возможность с помощью специальных методов в классах переопределять различные операторы языка. Имена таких методов включают двойное подчеркивание спереди и сзади.

Под операторами в данном контексте понимаются не только знаки +, -, *, /, обеспечивающие операции сложения, вычитания и др., но также специфика синтаксиса языка, обеспечивающая операции создания объекта, вызова объекта как функции, обращение к элементу объекта по индексу, вывод объекта и другое.

Мы уже использовали ряд методов перегрузки операторов. Это

В Python много других методов перегрузки операторов. В этом уроке рассмотрим еще несколько.

На самом деле перегрузка большинства операторов в пользовательских классах используется не так часто. Но сам факт наличия такой особенности объектно-ориентированного программирования требует отдельного рассмотрения темы.

Возможность перегрузки операторов обеспечивает схожесть пользовательского класса со встроенными классами Python. Ведь все встроенные типы данных Питона – это классы. В результате все объекты могут иметь одинаковые интерфейсы. Так если ваш класс предполагает обращение к элементу объекта по индексу, например a[0], то это можно обеспечить.

Пусть будет класс-агрегат B, содержащий в списке объекты класса A:

class A:
    def __init__(self, arg):
        self.arg = arg
 
    def __str__(self):
        return str(self.arg)
 
 
class B:
    def __init__(self, *args):
        self.aList = []
        for i in args:
            self.aList.append(A(i))
 
 
group = B(5, 10, 'abc')
 

Чтобы получить элемент списка, несомненно, мы можем обратиться по индексу к полю aList:

print(group.aList[1])

Однако куда интереснее извлекать элемент по индексу из самого объекта, а не из его поля:

class B:
    def __init__(self, *args):
        self.aList = []
        for i in args:
            self.aList.append(A(i))
 
    def __getitem__(self, i):
        return self.aList[i]
 
 
group = B(5, 10, 'abc')
print(group.aList[1])  # выведет 10
print(group[0])  # 5
print(group[2])  # abc

Это делает объекты класса B похожими на объекты встроенных в Python классов-последовательностей (списков, строк, кортежей). Здесь метод __getitem__ перегружает операцию извлечения элемента по индексу. Другими словами, этот метод вызывается, когда к объекту применяется операция извлечения элемента: объект[индекс].

Бывает необходимо, чтобы объект вел себя как функция. Это значит, если у нас есть объект a, то мы можем обращаться к нему в нотации функции, то есть ставить после него круглые скобки и даже передавать в них аргументы:

a = A()
a()
a(3, 4)

Метод __call__ автоматически вызывается, когда к объекту обращаются как к функции. Например, здесь во второй строке произойдет вызов метода __call__() некогоКласса:

объект = некийКласс()
объект([возможные аргументы])

Пример:

class Changeable:
    def __init__(self, color):
        self.color = color
 
    def __call__(self, newcolor):
        self.color = newcolor
 
    def __str__(self):
        return "%s" % self.color
 
 
canvas = Changeable("green")
frame = Changeable("blue")
 
canvas("red")
frame("yellow")
 
print(canvas, frame)

В этом примере с помощью конструктора класса при создании объектов устанавливается их цвет. Если требуется его поменять, то достаточно обратиться к объекту как к функции и в качестве аргумента передать новый цвет. Такой обращение автоматически вызовет метод __call__(), который, в данном случае, изменит атрибут color объекта.

В Python кроме метода __str__ есть схожий с ним по поведению, но более "низкоуровневый" метод __repr__. Оба метода должны возвращать строку.

Если в классе есть только метод __str__(), то при обращении к объекту в интерпретаторе без функции print(), он не будет вызываться:

>>> class A:
...     def __str__(self):
...             return "This is object of A"
... 
>>> a = A()
>>> print(a)
This is object of A
>>> a
<__main__.A instance at 0x7fe964a4cdd0>
>>> str(a)
'This is object of A'
>>> repr(a)
'<__main__.A instance at 0x7fe964a4cdd0>'

Метод __str__() вызывается при попытке преобразования объекта в строку с помощью встроенной функции str(). А функция print() так устроена, что сама вызывает str() для своих аргументов.

В Python есть встроенная функция repr(), которая также как str() преобразует объект в строку. Но "сырую" строку. Что это значит, попробуем понять с помощью примера:

>>> a = '3 + 2'
>>> b = repr(a)
>>> a
'3 + 2'
>>> b
"'3 + 2'"
>>> eval(a)
5
>>> eval(b)
'3 + 2'
>>> c = "Hello\nWorld"
>>> d = repr(c)
>>> c
'Hello\nWorld'
>>> d
"'Hello\\nWorld'"
>>> print(c)
Hello
World
>>> print(d)
'Hello\nWorld'

Функция eval() преобразует переданную строку в программный код, который тут же выполняется. Функция print() выполняет переход на новую строку, если встречает символ \n. Функция repr() выполняет действия, направленные на своего рода защиту строки от интерпретации, оставляет ее "сырой", т. е. в исходном виде. Еще раз:

>>> c = "Hello\nWorld"
>>> c # аналог print(repr(c))
'Hello\nWorld'
>>> print(c) # аналог print(str(c))
Hello
World

Если для вашего класса различия между сырым и обычным строковым представлением экземпляров не важны, желательно определять только метод __repr__. Он будет вызываться для всех случаев преобразования к строке:

>>> class A:
...     def __repr__(self):
...             return "It's obj of A"
... 
>>> a = A()
>>> a
It's obj of A
>>> repr(a)
"It's obj of A"
>>> str(a)
"It's obj of A"
>>> print(a)
It's obj of A

Если же нужен различающийся вывод данных, тогда в классе следует определить оба метода перегрузки операторов преобразования к строке.

Практическая работа

Напишите класс Snow по следующему описанию.

В конструкторе класса инициируется поле, содержащее количество снежинок, выраженное целым числом.

Класс включает методы перегрузки арифметических операторов: __add__ – сложение, __sub__ – вычитание, __mul__ – умножение, __truediv__ – деление. В классе код этих методов должен выполнять увеличение или уменьшение количества снежинок на число n или в n раз. Метод __truediv__ перегружает обычное /, а не целочисленное // деление. Однако пусть в методе происходит округление значения до целого числа.

Класс включает метод make_snow, который помимо self принимает число снежинок в ряду и возвращает строку вида "*****\n*****\n*****…", где количество снежинок между '\n' равно переданному аргументу, а количество рядов вычисляется, исходя из общего количества снежинок.

Вызов экземпляра класса Snow в нотации функции с одним аргументом, должен приводить к перезаписи значения поля, в котором хранится количество снежинок, на переданное в качестве аргумента значение.

Курс с примерами решений практических работ:
pdf-версия


Объектно-ориентированное программирование на Python




Все разделы сайта