Reputation: 1919
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
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
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