a+b
равнозначно import operator; operator.add(a,b)
. Он начинается с вызова a.__add__(b)
, а затем, при необходимости, b.__radd__(a)
. Но ifsubclass(type(b), type(a))
, затем b.__radd__(a)
называется первым.
На основе docs on "special" methods:
Относительно __add__()
:
__add__()
вызывается для выполнения арифметических операций двоичного "+" операции. Например, для оценки выражения x + y, где x является экземпляром класса, который имеет метод __add__()
, вызывается x.__add__(y)
.
Если один из этих методов не поддерживает операцию с прилагаемыми аргументами, он должен вернуть NotImplemented.
Что касается __radd__()
:
Эти функции вызываются только если левый операнд не поддерживает соответствующую операцию и операнды различных типов. Например, чтобы оценить выражение x + y, где y является экземпляром класса, который имеет метод __radd__()
, вызывается y.__radd__(x)
, если x.__add__(y)
возвращает NotImplemented.
Если тип нужного операнда является подклассом типа левого операнда, и этот подкласс предоставляет отраженный метод для операции, этот метод будет вызываться перед неодушевленным методом левого операнда. Такое поведение позволяет подклассам переопределять операции своих предков.
Объяснение с примерами на основе поведения:
Случай 1:
>>> print 1+c
('C.__radd__', <__main__.C instance at 0x7ff5631397a0>, 1)
reversed result
Эти функции radd
называются только если левый операнд не поддерживает соответствующая операция и операнды имеют разные типы. В этом случае 1
не поддерживает добавление класса, следовательно, он возвращается к функции __radd__()
класса C
. В случае __radd__
не реализовать в C()
классе, он упал бы обратно __add__()
Вариант 2:
>>> 1 .__add__(c)
NotImplemented
>>> c .__add__(1)
('C.__add__', <__main__.C instance at 0x7ff563139830>, 1)
'result'
1 .__add__(c)
дает NotImplemented
, как 1
имеет int
типа и add
из int
класса не поддерживает add
с объектом класса C. Но c .__add(1)
запустить, потому что класс C()
поддерживает это.
Случай 3:
>>> int.__add__(1, c)
NotImplemented
>>> C.__add__(c, 1)
('C.__add__', <__main__.C instance at 0x7ff5610add40>, 1)
'result'
Подобно case 2
. Но здесь вызов выполняется через класс с первым аргументом как объектом этого класса. Поведение будет таким же.
Случай 4:
>>> int.__add__(c, 1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: descriptor '__add__' requires a 'int' object but received a 'instance'
>>> C.__add__(1, c)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unbound method __add__() must be called with C instance as first argument (got int instance instead)
И наоборот из case 3
. Как очищается от трассировки стека, __add__
ожидал, что объект вызывающего класса будет первым аргументом, в противном случае это приведет к исключению.
Er, потому что вы назвали '__add__' напрямую? Как вы заявляете, Python делает резервную копию только при использовании оператора '+'. –
Угадайте, что я не знал, что резервное копирование не будет использоваться, если я использовал '__add__' напрямую. – Korred