Арифметические операции
Рассмотрим арифметические операции на примере сложения. Возможные варианты сложения:
x + y # x и y - объекты одного класса
x + 12 # складываем объект с другим типом
12 + x # складываем другой тип с объектом
x += y # сложение с присваиванием
__add__, __radd__, __iadd__
Чем отличаются эти методы и какие операции реализуют?
Пусть в классе Drob реализован метод __add__, который складывает две дроби.
Что делать, если хотим сложить дробь с числом опишем потом.
Что делать, если хотим сложить число с дробью: 3 + Drob(1, 2)
. Метод __add__ класса Drob будет вызван, если левый операнд - дробь. Если дробь прибавляем к int, то должен вызываться метод __add__ класса int (который ничего не знает о классе Drob).
Метод __radd__ класса вызывается, когда экземпляр класса находится справа от +, а слева от + не является экземпляром класса.
>>> class Commuter:
... def __init__(self, val):
... self.val = val
... def __add__(self, other):
... print('add', self.val, other)
... return self.val + other # если other экземпляр этого же класса, то получим вызов __radd__
... def __radd__(self, other):
... print('radd', self.val, other)
... return other + self.val
...
>>> x = Commuter(88)
>>> y = Commuter(99)
>>> x + 1 # __add__: экземпляр + не_экземпляр
add 88 1
89
>>> 1 + y # __radd__: не_экземпляр + экземпляр
radd 99 1
100
>>> x + y # __add__: экземпляр + экземпляр
add 88 <__main__.Commuter instance at 0x02630910>
radd 99 88
187
Разберем порядок вызовов при x+y. Так как х слева и у него есть метод __add__, вызывается этот метод.
При вызове метода __add__ доходим до вычисления self.val + other
и self.val тут число, а other - объект класса, значит вызывается other.__radd(self.val)
.
Если результатом операции должен быть экземпляр класса, то его нужно вернуть. Если мы пишем неизменяемый тип, то создать новый экземпляр.
Заметьте, если не проверить в __add__, что other тоже экземпляр класса, то получим бесконечную рекурсию:
>>> class Commuter: # Тип класса распространяется на результат
... def __init__(self, val):
... self.val = val
... def __add__(self, other):
... if isinstance(other, Commuter): other = other.val
... return Commuter(self.val + other)
... def __radd__(self, other):
... return Commuter(other + self.val)
... def __str__(self):
... return '<Commuter: %s>' % self.val
...
>>> x = Commuter(88)
>>> y = Commuter(99)
>>> print(x + 10) # Результат – другой экземпляр класса Commuter
<Commuter: 98>
>>> print(10 + y)
<Commuter: 109>
>>> z = x + y # Нет вложения: не происходит рекурсивный вызов __radd__
>>> print(z)
Какие методы будут вызваны, если в классе Drob1 реализован метод __add__, в классе Drob2 реализован метод __radd__, и вычисляется выражение Drob1(1, 2) + Drob2(1, 2) (если классы не связаны отношением наследования)? Вызывается метод __add__ для левого операнда.
Правосторонние методы реализуют, если в класс должен поддерживать перестановку операндов.
Классу Drob такой метод нужен. Классу Student - скорее всего нет.
+= - изменение объекта
Оператор += реализован через метод __iadd__. Если его нет, вызывается __add__.
В __iadd__ можно изменять сам объект, не создавая новые объекты (как list.extend).
>>> class Number1:
... def __init__(self, val):
... self.val = val
... def __iadd__(self, other): # __iadd__ явно реализует операцию x += y
... self.val += other # Обычно возвращает self
... return self
...
>>> x = Number1(5)
>>> x += 1
>>> x += 1
>>> x.val
7
>>> class Number2:
... def __init__(self, val):
... self.val = val
... def __add__(self, other): # __add__ - как крайнее средство: x=(x + y)
... return Number2(self.val + other) # Распространяет тип класса
...
>>> x = Number2(5)
>>> x += 1
>>> x += 1
>>> x.val
7
Другие арифметические операции имеют аналогичный набор методов.