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

北战南征 提交于 2021-02-04 06:13:17

问题


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.


回答1:


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



回答2:


Proxying __dunder__ methods is tricky. I would use a descriptor object, which will work much more cleanly with the potential arcana of attribute access than other approaches.

class Proxy:
    def __set_name__(self, owner, name):
        self.attr = name
    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        try:
            proxy = obj._proxy
        except AttributeError:
            raise AttributeError('tried to access proxy field on object with no _proxy')
        return getattr(proxy, self.attr)

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
        self._proxy = self.a
    __add__ = Proxy()
    __sub__ = Proxy()

b = B(A())

An example in the ipython repl:

In [6]: b = B(A())

In [7]: b + b
Out[7]: 9

In [8]: b - b
Out[8]: 8

In [9]: b * b
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-19-cb34cccc83f5> in <module>
----> 1 b * b

TypeError: unsupported operand type(s) for *: 'B' and 'B'

If you wanted to expand on this approach, Proxy could take a fieldname from which it proxies off of, so you could have something like:

class Proxy:
    def __init__(self, proxy_field):
        self.prox_field = proxy_field
    def __set_name__(self, owner, name):
        self.attr = name
    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        try:
            proxy = getattr(obj, self.proxy_field)
        except AttributeError:
            raise AttributeError(f'tried to access proxy field on object with no {self.proxy_field} attribute')
        return getattr(proxy, self.attr)

class B:

    def __init__(self, foo, bar):
        self.foo = foo
        self.bar = bar

    __add__ = Proxy('foo')
    __sub__ = Proxy('bar')


来源:https://stackoverflow.com/questions/59742625/dynamically-adding-builtin-methods-to-point-to-a-propertys-built-ins

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!