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