在Python 3中使用partial在元类中创建实例方法

8 投票
3 回答
2008 浏览
提问于 2025-04-16 12:47

我正在使用 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))

撰写回答