Арифметические операции

Рассмотрим арифметические операции на примере сложения. Возможные варианты сложения:

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

Другие арифметические операции имеют аналогичный набор методов.

results matching ""

    No results matching ""