Перегрузка операторов в Python
Перегрузка операторов в Python – это возможность с помощью специальных методов в классах переопределять различные операторы языка. Имена таких методов включают двойное подчеркивание спереди и сзади.
Под операторами в данном контексте понимаются не только знаки +, -, *, /, обеспечивающие операции сложения, вычитания и др., но также специфика синтаксиса языка, обеспечивающая операции создания объекта, вызова объекта как функции, обращение к элементу объекта по индексу, вывод объекта и другое.
Мы уже использовали ряд методов перегрузки операторов. Это
-
__init__()
– конструктор объектов класса, вызывается при создании объектов -
__del__()
– деструктор объектов класса, вызывается при удалении объектов -
__str__()
– преобразование объекта к строковому представлению, вызывается, когда объект передается функциямprint()
иstr()
-
__add__()
– метод перегрузки оператора сложения, вызывается, когда объект участвует в операции сложения будучи операндом с левой стороны -
__setattr__()
– вызывается, когда атрибуту объекта выполняется присваивание
В Python много других методов перегрузки операторов. В этом уроке рассмотрим еще несколько.
На самом деле перегрузка большинства операторов в пользовательских классах используется не так часто. Но сам факт наличия такой особенности объектно-ориентированного программирования требует отдельного рассмотрения темы.
Возможность перегрузки операторов обеспечивает схожесть пользовательского класса со встроенными классами Python. Ведь все встроенные типы данных Питона – это классы. В результате все объекты могут иметь одинаковые интерфейсы. Так если ваш класс предполагает обращение к элементу объекта по индексу, например a[0]
, то это можно обеспечить.
class Pupil: __identifier = 1000 def __init__(self, pupil): self.name, self.surname = pupil.split() self.id = Pupil.__identifier Pupil.__identifier += 1 def __str__(self): return f'{self.name[0]}. {self.surname} - {self.id}' class SchoolClass: def __init__(self, *pupils): self.pupils = pupils def __getitem__(self, item): return self.pupils[item] a5 = SchoolClass(Pupil('Иван Иванов'), Pupil('Петр Петров'), Pupil('Мария Машина')) print(a5.pupils[2]) # М. Машина - 1002 print(a5[1]) # П. Петров - 1001 print(a5[0].name) # Иван
В примере, чтобы получить элемент поля pupils школьного класса, несомненно, мы можем непосредственно обратиться к этому полю. Однако куда интереснее извлекать элемент по индексу из самого объекта, а не из его поля.
Это делает объекты пользовательского класса похожими на объекты встроенных в Python классов-последовательностей (списков, строк, кортежей). Здесь метод __getitem__
перегружает операцию извлечения элемента по индексу. Другими словами, этот метод вызывается, когда к объекту применяется операция извлечения элемента: объект[индекс]
.
Если в классе описан метод __getitem__
, то его экземпляры могут передаваться циклу for
:
for person in a5: print(person)
Бывает необходимо, чтобы объект вел себя как функция. Это значит, если у нас есть объект 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
Если же нужен различающийся вывод данных, тогда в классе следует определить оба метода перегрузки операторов преобразования к строке.
Практическая работа
Задание 1
Напишите класс Snow по следующему описанию.
В конструкторе класса инициируется поле, содержащее количество снежинок, выраженное целым числом.
Класс включает методы перегрузки арифметических операторов: __add__
– сложение, __sub__
– вычитание, __mul__
– умножение, __truediv__
– деление. В классе код этих методов должен выполнять увеличение или уменьшение количества снежинок на число n или в n
раз. Метод __truediv__
перегружает обычное /
, а не целочисленное //
деление. Однако пусть в методе происходит округление значения до целого числа.
Класс включает метод make_snow, который помимо self принимает число снежинок в ряду и возвращает строку вида "*****\n*****\n*****…"
, где количество снежинок между '\n'
равно переданному аргументу, а количество рядов вычисляется, исходя из общего количества снежинок.
Вызов экземпляра класса Snow в нотации функции с одним аргументом, должен приводить к перезаписи значения поля, в котором хранится количество снежинок, на переданное в качестве аргумента значение.
Задание 2
Перепишите приведенный в уроке пример с классами "Ученик" и "Школьный класс" таким образом, чтобы он в большей степени отражал композиционный подход. Для этого объекты-ученики должны создаваться внутри класса SchoolClass. Также реализуйте метод __call__
, вызов которого добавлял бы еще одного ученика в экземпляр класса.
PDF-версия курса с примерами решений практических работ