Перегрузка операторов в Python
Перегрузка операторов в Python – это возможность с помощью специальных методов в классах переопределять различные операторы языка. Имена таких методов включают двойное подчеркивание спереди и сзади.
Под операторами в данном контексте понимаются не только знаки +, -, *, /, обеспечивающие операции сложения, вычитания и др., но также специфика синтаксиса языка, обеспечивающая операции создания объекта, вызова объекта как функции, обращение к элементу объекта по индексу, вывод объекта и другое.
Мы уже использовали ряд методов перегрузки операторов. Это
-
__init__()
– конструктор объектов класса, вызывается при создании объектов -
__del__()
– деструктор объектов класса, вызывается при удалении объектов -
__str__()
– преобразование объекта к строковому представлению, вызывается, когда объект передается функциямprint()
иstr()
-
__add__()
– метод перегрузки оператора сложения, вызывается, когда объект участвует в операции сложения будучи операндом с левой стороны -
__setattr__()
– вызывается, когда атрибуту объекта выполняется присваивание
В 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-версия