Заболекарь
Мегакрендель: заколебарь, жаболекарь, зомболекарь, лежебокарь
О магических методах в пайтоне.

Многие наивно думают, что для любых двух объектов a, b выражение a+b есть просто синтактический сахар для a.__add__(b). Это неправда. Другие наивно думают, что это синтактический сахар для a.__class__.__add__(a, b). Это тоже неправда.

Легко составить пример, в котором все три выражения будут вести себя по-разному. Попробуйте сами, если не получится, смотрите отгадку.


class A(object):
   def __init__(self):
      self.__add__ = lambda other: 2
   def __add__(self, other):
      return 3

class B(A):
   def __radd__(self, other):
      return 1

a, b = A(), B()

print(a+b) # 1
print(a.__add__(b)) # 2
print(A.__add__(a, b)) # 3



Здесь принципиально, что B наследует A. Без этого трюк не сработает, a+b вернёт 3. Логика такая: если B наследует A, значит, автор класса B знал про класс A и что-то по этому поводу подумал, а вот автор класса A мог про класс B вообще не догадываться. Значит, лучше сначала вызвать B.__radd__, и только если такого метода нет или он возвращает NotImplemented, тогда можно попробовать A.__add__. В противном же случае первым делом вызывается A.__add__, и только если его нет или он возвращает NotImplemented, тогда можно попробовать B.__add__. Кстати, если ни A.__add__, ни B.__radd__ не существует, то поведение у a+b и A.__add__(a, b) тоже будет разное: первое кинет TypeError, второе AttributeError.

Для чего нужны __radd__, __rmul__ и им подобные методы? Они нужны, чтобы выражения вроде 1 + numpy.array([1, 2, 3]) или 1 + decimal.Decimal('123') или 1 + fractions.Fraction(1, 2) работали по-человечески. Разумеется, ту же проблему можно было решить другими способами. Скажем, в чудесном языке Julia дела обстоят так:

import Base.+

type A end
type B end

+(::A, ::B) = 4
+(::A, ::A) = 5

a, b = A(), B()



Теперь a+b вернёт 4, a+a вернёт 5, b+a и b+b кинут MethodError, но доопределить их можно в любой момент, когда они понадобятся.

Итак, можно было сделать иначе. Но сделали вот так. Эти методы в пайтоне не зря зовут магическими, и магия эта черна.

Также принципиально, чтобы A наследовал от object, без этого трюк не сработает, a+b вернёт 2. В третьем пайтоне не ошибётесь, а во втором, как известно, легко случайно создать классы, от object не наследующие.

@темы: root@глупыйпингвин:~#