【问题标题】:Dynamically adding builtin methods to point to a property's built-ins [duplicate]动态添加内置方法以指向属性的内置方法[重复]
【发布时间】:2020-01-14 22:24:10
【问题描述】:

我有几个类和一个函数:

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

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

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

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

【问题讨论】:

    标签: python partial-application


    【解决方案1】:

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

    下面定义的函数delegate 通过向B 类添加一个方法来工作。这个方法接受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
    

    【讨论】:

    • 很公平。不过,我喜欢您的解决方案,我想如果您愿意,可以将 attr_name 添加到您的 Proxy 类并动态加载它,以允许将不同的方法委托给不同的属性。
    • 是的,它绝对是可扩展的。
    【解决方案2】:

    代理__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 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'
    

    如果您想扩展这种方法,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')
    

    【讨论】:

      猜你喜欢
      • 2018-03-21
      • 2015-06-21
      • 2015-06-16
      • 2016-01-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-07-06
      相关资源
      最近更新 更多