0

Consider this class where methods are added depending on the value of some parameter:

class Something(object):
    def __init__(self, value, op='+'):
        self.x = value
        if op == '+':
            self.add = self._operator
        elif op == '-':
            self.sub = self._operator

    def _operator(self, other):
        return self.x * other
    
x = Something(10)
x.add(3)
# 30

Now, I would like to use + and - operators instead of .add() or .sub() notation. For this, I would write:

class Something(object):
    def __init__(self, value, op='+'):
        self.x = value
        if op == '+':
            self.__add__ = self._operator
            self.__radd__ = self._operator
        elif op == '-':
            self.__sub__ = self._operator
            self.__rsub__ = self._operator

    def _operator(self, other):
        return self.x * other           

x = Something(10)
print(x + 3)

But instead of 30, I get:

TypeError: unsupported operand type(s) for +: 'Something' and 'int'

despite:

print(dir(x))
# ['__add__', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__radd__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_operator', 'x']

What is going wrong and how could I solve this?

EDIT

This is different from Overriding special methods on an instance in that I am not trying to add a special method to an instance of an object after the object is created, i.e.:

x = Something(10)
x.__add__ = ...

But rather during class.__init__() although, quite admittedly, both the source of the error and the solution are very similar.

Community
  • 1
  • 1
norok2
  • 25,683
  • 4
  • 73
  • 99

2 Answers2

1

Special methods often avoid the attribute lookup mechanism. They don't check the instance attributes but directly look in the class.

3.3.10. Special method lookup

For custom classes, implicit invocations of special methods are only guaranteed to work correctly if defined on an object’s type, not in the object’s instance dictionary.

[...]

Bypassing the __getattribute__() machinery in this fashion provides significant scope for speed optimisations within the interpreter, at the cost of some flexibility in the handling of special methods (the special method must be set on the class object itself in order to be consistently invoked by the interpreter).

However you can still defer to instance attributes in the special methods.

Expanding your example (there might be more elegant ways):

class Something(object):
    def __init__(self, value, op='+'):
        self.x = value
        if op == '+':
            self._add = self._operator
            self._radd = self._operator
        elif op == '-':
            self._sub = self._operator
            self._rsub = self._operator

    def _operator(self, other):
        return self.x * other

    def __add__(self, other):
        try:
            return self._add(other)
        except AttributeError:
            return NotImplemented

    # etc. for __radd__, __sub__ and __rsub__

x = Something(10)
print(x + 3)
Community
  • 1
  • 1
MSeifert
  • 145,886
  • 38
  • 333
  • 352
0

Well, don't bother so much, do what you can, so just use:

class Something(object):
    def __init__(self, value):
        self.x = value

    def __add__(self, other):
        return self.x * other

    def __sub__(self, other):
        return self.x * other

x = Something(10)
print(x + 3)

Output:

30

It works with - as well.

U13-Forward
  • 69,221
  • 14
  • 89
  • 114
  • This workaround does not address the original issue. Also, it would be more elegant to do `__sub__ = __add__` on the body of the class. – norok2 Jun 27 '19 at 11:08