Reputation: 91
I am trying to overload several operators at once using the __getattr__
function. In my code, if I call foo.__add__(other)
it works as expected, but when I try foo + bar
, it does not. Here is a minimal example:
class Foo():
def add(self, other):
return 1 + other
def sub(self, other):
return 1 - other
def __getattr__(self, name):
stripped = name.strip('_')
if stripped in {'sub', 'add'}:
return getattr(self, stripped)
else:
return
if __name__=='__main__':
bar = Foo()
print(bar.__add__(1)) # works
print(bar + 1) # doesn't work
I realize that it would be easier in this example to just define __add__
and __sub__
, but that is not an option in my case.
Also, as a small side question, if I replace the line:
if stripped in {'sub', 'add'}:
with
if hasattr(self, name):
the code works, but then my iPython kernel crashes. Why does this happen and how could I prevent it?
Upvotes: 6
Views: 759
Reputation: 91
I've found a semi-satisfying work-around that does not involve meta-classes:
class Foo():
def add(self, other):
return 1 + other
def sub(self, other):
return 1 - other
# Add operators to the type object
def get_operator(name):
return getattr(Foo, name.strip('_'))
for op in ['__add__', '__sub__']:
setattr(Foo, op, get_operator(op))
if __name__=='__main__':
bar = Foo()
print(bar + 2)
print(bar - 2)
Upvotes: 1
Reputation: 6665
Also, as a small side question, if I replace the line: [...]
hasattr
calls __getattr__
under the hood. Which explains what you saw when doing if hasattr(self, name):
, you actually enter an infinite recursion since you have overwritten __getattr__
.
See for yourself
class O:
def __getattr__(self, attr):
print('yooo')
return super().__getattr__(attr)
# self.__getattr__(attr) -> RecursionError
>>> hasattr(O(), 'ya')
yooo
False
Upvotes: 4
Reputation: 114478
This is happening because python operators use an optimization to look up the function implementing the operator. The following lines are roughly equivalent:
foo + 1
type(foo).__add__(foo, 1)
Operators are found specifically on the class object only, never on the instance.
bar.__add__(1)
calls __getattr__
to find the missing attribute on bar
. This works because it bypasses normal operator lookup procedures.
bar + 1
calls Foo.__add__(bar, 1)
followed by int.__radd(1, bar)
. The first attribute lookup fails, and the second option raises TypeError
.
Upvotes: 7