DeanLa
DeanLa

Reputation: 1919

overriding magic methods with __getattr__

I have a class, that is a container for members. all members are of the same type.

class A(int):
    def __init__(self, n):
        super().__init__()
        self.n = n

    def do(self):
        print('adding 10')
        return self.n + 10


class B:
    def __init__(self, a1, a2):
        self.a = [a1, a2]

    def __getattr__(self, item):
        return getattr(self.a[0], item)


a1 = A(10)
a2 = A(5)
b = B(a1, a2)

the __getattr__ overrides the do method:

In[7]: b.do()
adding 10
Out[7]: 20

and even overrides __add__ when called explicitly

In[8]: b.__add__(1)
Out[8]: 11

but __add__ fails when called as +

In[9]: b+1
TypeError: unsupported operand type(s) for +: 'B' and 'int'

How can I override magic methods to work as expected?

Upvotes: 1

Views: 426

Answers (2)

Aaron Zolnai-Lucas
Aaron Zolnai-Lucas

Reputation: 207

I wanted to add an answer to Maximouse's comment to illustrate that metaclasses do not solve this issue. Given:

class Meta(type):
    def __getattr__(self, name):
        return "meta-attr"

class X(metaclass=Meta):
    def __getattr__(self, name):
        return "attr"

then these work:

>>> X().__add__(X())
"attr"
>>> X.__add__(X(), X())
"meta-attr"

but this fails:

>>> X() + X()
TypeError: unsupported operand type(s) for +: 'Derived' and 'Derived'
    ...

meaning metaclasses don't achieve OP's goals. Using dis, we can see that under the hood python never even does an attribute lookup, it simply calls BINARY_ADD:

>>> from dis import dis
>>> a, b = X(), X()
>>> dis("a + b")
  1           0 LOAD_NAME                0 (a)
              2 LOAD_NAME                1 (b)
              4 BINARY_ADD
              6 RETURN_VALUE

(tested on python 3.8)

Upvotes: -1

Jean-François Fabre
Jean-François Fabre

Reputation: 140276

The reason why operators aren't handled by __getattr__ is covered in Why is __getattr__ capable of handling built-in operator overloads in python 2.x?

In new-style classes the special methods are always looked up in the class(implicit lookup) not instance.

My workaround (not perfect, but allows to make it work) would be to explicitly define the required dunder methods in B, and explicitly call __getattr__ on them:

class B:
    def __init__(self, a1, a2):
        self.a = [a1, a2]

    def __add__(self,other):
        return self.__getattr__("__add__")(other)

    def __getattr__(self, item):
        return getattr(self.a[0], item)

Upvotes: 2

Related Questions