drmaettu
drmaettu

Reputation: 91

Overloading operators using __getattr__ in Python

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

Answers (3)

drmaettu
drmaettu

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

keepAlive
keepAlive

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

Mad Physicist
Mad Physicist

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

Related Questions