Перегрузка операторов в языке 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(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 объекта.
Помимо метода __str__(), в Python есть аналогичный по функционалу, но более "низкоуровневый" метод __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() при попытке преобразования объекта в строку. Это значит, что метод __str__() фактически является перегрузкой str(), а не print(). Функция print() вызывает str() для преобразования своих аргументов.
Встроенная функция 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() и str() несущественна. Следовательно, удобнее создавать в классах только метод __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__() для деления. Эти методы должны увеличивать или уменьшать количество снежинок на заданное число или в указанное число раз. Метод __truediv__() реализует операцию обычного деления (/). Однако резульататы будут округляться до ближайшего целого.
Внесите в класс метод makeSnow(), способный принимать сам объект и число снежинок в строке, и возвращающий строку в формате "*****\n*****\n*****…", где количество снежинок между '\n' соответствует аргументу, переданному этому методу. Количество строк определяется исходя из общего количества снежинок.
Обращение к объекту класса Snow как к функции с передачей одного аргумента должно вызывать перезапись значения, представляющего количество снежинок, в объекте на новое переданное значение.