Search code examples
pythonpartial-application

Dynamically adding builtin methods to point to a property's built-ins


I have a couple classes and a function:

from functools import partial 

def fn(other, self, name):
    print(f"calling {name} with {other}")
    func =  getattr(self.a, name)
    return func(other)

class A:

    def __add__(self, other):
        return 9

    def __mul__(self, other):
        return 7

    def __sub__(self, other):
        return 8

class B:

    def __init__(self,a):
        self.a = a

        for name  in ['add', 'sub']:
            name = f"__{name}__"
            p = partial(fn, self=self,name=name)
            setattr(self, name, p)
            p.__name__ = name

I want to be able to use the forward the magic methods to an existing property. I don't want to inherit the class because I don't want all the builtins. just a couple. For instance I might want to use multiply from a different class. I'm trying to avoid coding like this:

def __add__(self, other):
    self.a.__add__(other)

using the above code I receive the following:

>>> b = B(A())
>>> b + 3
TypeError                                 Traceback (most recent call last)
<ipython-input-40-fa904b7bb783> in <module>
----> 1 b + 3
      2 

TypeError: unsupported operand type(s) for +: 'B' and 'int'
>>> b.__add__(3)
calling __add__ with 3
9

Maybe I'm missing something simple but I can't find a way to dynamically add the builtin function.


Solution

  • The main problem to get around is that magic methods like __add__ are looked up on the class, not on the object itself; otherwise you could just write self.__add__ = a.__add__ in the __init__ method. To get around this, we need to declare methods on the class B, not on individual instances of it.

    The function delegate defined below works by adding a method to the B class. This method takes self which will be a B instance, so it has to dynamically load the a attribute and then its __add__ method.

    class A:
        def __add__(self, other):
            return 9
        def __mul__(self, other):
            return 7
        def __sub__(self, other):
            return 8
    
    class B:
        def __init__(self, a):
            self.a = a
    
    def delegate(cls, attr_name, method_name):
        def delegated(self, *vargs, **kwargs):
            a = getattr(self, attr_name)
            m = getattr(a, method_name)
            return m(*vargs, **kwargs)
        setattr(cls, method_name, delegated)
    
    delegate(B, 'a', '__add__')
    delegate(B, 'a', '__sub__')
    

    Example:

    >>> b = B(A())
    >>> b + 3
    9
    >>> b - 4
    8