动态添加内置方法以指向 属性 的内置方法

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

我有几个 classes 和一个函数:

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

我希望能够使用将魔术方法转发到现有 属性。我不想继承 class 因为我不想要所有的内置函数。只是一对夫妇。例如,我可能想使用来自不同 class 的乘法。我试图避免像这样编码:

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

使用上面的代码我收到以下信息:

>>> 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

也许我遗漏了一些简单的东西,但我找不到动态添加内置函数的方法。

要解决的主要问题是像 __add__ 这样的魔术方法是在 class 上查找的,而不是在对象本身上;否则你可以在 __init__ 方法中写 self.__add__ = a.__add__ 。为了解决这个问题,我们需要在 class B 上声明方法,而不是在它的单个实例上声明方法。

下面定义的函数 delegate 通过向 B class 添加一个方法来工作。此方法采用 self,这将是一个 B 实例,因此它必须动态加载 a 属性,然后加载其 __add__ 方法。

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__')

示例:

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

代理 __dunder__ 方法很棘手。我会使用描述符对象,与其他方法相比,它可以更干净地处理属性访问的潜在奥秘。

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())

ipython 回复中的示例:

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'

如果你想扩展这种方法,Proxy 可以使用它代理的字段名,所以你可以有类似的东西:

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')