在Python 3中使用partial在元类中创建实例方法
我正在使用 metaclass(类的模板)来创建一个实例方法,目的是简化一个已经存在的实例方法。不过,问题是 partial(部分应用函数)在实例方法上不太好用。这是我想实现的一个简单例子:
from functools import partial
class Aclass(object):
def __init__(self, value):
self._value = value
def complex(self, a, b):
return a + b + self._value
class Atype(type):
def __new__(cls, name, bases, attrs):
return super(Atype, cls).__new__(cls, name, (Aclass, ) + bases, attrs)
def __init__(cls, name, bases, attrs):
setattr(cls, 'simple', partial(cls.complex, b=1))
class B(metaclass=Atype):
pass
b = B(10)
print(b.complex(1, 2))
print(b.simple(1))
输出结果是:
13
Traceback (most recent call last):
File "metatest.py", line 22, in <module>
print(b.simple(1))
TypeError: complex() takes exactly 3 non-keyword positional arguments (1 given)
我通过使用 lambda(匿名函数)解决了这个问题,改成了:
setattr(cls, 'simple', partial(cls.complex, b=1))
变成:
setattr(cls, 'simple', lambda self, x: cls.complex(self, x, b=1))
但这样看起来不太好,而且在处理可选参数时也有问题。
我可以在实例的 __init__
方法中创建这些方法,但我觉得在类的 __init__
中使用 metaclass 来做会更合理,也更高效。
有没有好的方法可以正确地做到这一点呢?
3 个回答
1
问题在于,functools.partial()
返回的对象是一个可调用的对象,而不是一个真正的函数。所以在这个情况下,Python 对于一个非函数的东西试图像函数一样使用并不在意。一个解决办法是创建一个函数来包装这个 partial
对象。
class Atype(type):
def __init__(cls, name, bases, attrs):
simple = partial(cls.complex, b=1)
setattr(cls, 'simple', lambda cls, a: simple(cls, a))
jsbueno 提出的解决方案(重新实现了 partial
,返回一个真正的函数)是个不错的主意。不过我真的不知道为什么 functools.partial()
不这样工作;在这个情况下不能使用它真是个让人意外的坑。
3
与其使用 partial
,我会这样定义一个类 Atype
:
class Atype(type):
def __new__(cls, name, bases, attrs):
return super(Atype, cls).__new__(cls, name, (Aclass, ) + bases, attrs)
def __init__(cls, name, bases, attrs):
def simple(self, a):
return cls.complex(self, a, 1)
setattr(cls, 'simple', simple)
而且 __init__()
方法也可以写得更简洁:
def __init__(cls, name, bases, attrs):
setattr(cls, 'simple', lambda self, a: cls.complex(self, a, 1))
6
嗯,我对Python 3的方法处理还不是很熟悉——我想到的最简单的办法就是重写一下partial
,让它能保留原始调用中的第一个参数,然后再插入“部分”参数。
这个方法在你的例子中有效,但还需要用更复杂的情况来测试一下。
from functools import wraps
class Aclass(object):
def __init__(self, value):
self._value = value
def complex(self, a, b):
return a + b + self._value
def repartial(func, *parameters, **kparms):
@wraps(func)
def wrapped(self, *args, **kw):
kw.update(kparms)
return func(self, *(args + parameters), **kw)
return wrapped
class Atype(type):
def __new__(cls, name, bases, attrs):
return super(Atype, cls).__new__(cls, name, (Aclass, ) + bases, attrs)
def __init__(cls, name, bases, attrs):
setattr(cls, 'simple', repartial(cls.complex, b=1))
class B(metaclass=Atype):
pass
b = B(10)
print(b.complex(1, 2))
print(b.simple(1))