The answer is found in the two types of class that Python has.
The first code-snippet you provided uses a legacy "old-style" class (you can tell because it doesn't subclass anything - there's nothing before the colon). Its semantics are peculiar. In particular, you can add a special method to an instance:
class Foo:
def __init__(self, num):
self.num = num
def _fn(other):
return self.num + other.num
self.__add__ = _fn
and get a valid response:
>>> f = Foo(2)
>>> g = Foo(1)
>>> f + g
3
But, subclassing dict
means you are generating a new-style class. And the semantics of operator overloading are different:
class Foo (object):
def __init__(self, num):
self.num = num
def _fn(other):
return self.num + other.num
self.__add__ = _fn
>>> f = Foo(2)
>>> g = Foo(1)
>>> f + g
Traceback ...
TypeError: unsupported operand type(s) for +: 'Foo' and 'Foo'
To make this work with new-style classes (which includes subclasses of dict
or just about any other type you will find), you have to make sure the special method is defined on the class. You can do this through a metaclass:
class _MetaFoo(type):
def __init__(cls, name, bases, args):
def _fn(self, other):
return self.num + other.num
cls.__add__ = _fn
class Foo(object):
__metaclass__ = _MetaFoo
def __init__(self, num):
self.num = num
>>> f = Foo(2)
>>> g = Foo(1)
>>> f+g
3
Also, the semantic difference means that in the very first case I could define my local add method with one argument (the self
it uses is captured from the surrounding scope in which it is defined), but with new-style classes, Python expects to pass in both values explicitly, so the inner function has two arguments.
As a previous commenter mentioned, best to avoid old-style classes if possible and stick with new-style classes (old-style classes are removed in Python 3+). Its unfortunate that the old-style classes happened to work for you in this case, where new-style classes will require more code.
Edit:
You can also do this more in the way you originally tried by setting the method on the class rather than the instance:
class Foo(object):
def __init__(self, num):
self.num = num
setattr(Foo, '__add__', (lambda self, other: self.num + other.num))
>>> f = Foo(2)
>>> g = Foo(1)
>>> f+g
3
I'm afraid I sometimes think in Metaclasses, where simpler solutions would be better :)