动态将可调用对象添加为类的实例“方法”

7 投票
2 回答
2683 浏览
提问于 2025-04-17 13:40

我实现了一个元类,这个元类可以拆解用它创建的类的属性,并根据这些属性的数据构建方法,然后把这些动态创建的方法直接附加到类对象上(这个类主要是为了方便定义网页表单对象,以便在网页测试框架中使用)。这个功能一直运行得很好,但现在我需要添加一种更复杂的方法。为了保持代码的整洁,我把它实现成了一个可调用的类。不幸的是,当我尝试在一个实例上调用这个可调用的类时,它被当作类属性对待,而不是实例方法,调用时只接收到它自己的 self。我明白为什么会这样,但我希望能找到比我想到的更好的解决方案。以下是问题的简化示例:

class Foo(object):
    def __init__(self, name, val):
        self.name = name
        self.val = val
        self.__name__ = name + '_foo'
        self.name = name
    # This doesn't work as I'd wish
    def __call__(self, instance):
        return self.name + str(self.val + instance.val)

def get_methods(name, foo_val):
    foo = Foo(name, foo_val)
    def bar(self):
        return name + str(self.val + 2)
    bar.__name__ = name + '_bar'
    return foo, bar

class Baz(object):
    def __init__(self, val):
        self.val = val

for method in get_methods('biff', 1):
    setattr(Baz, method.__name__, method)
baz = Baz(10)
# baz.val == 10
# baz.biff_foo() == 'biff11'
# baz.biff_bar() == 'biff12'

我考虑过:

  1. 使用描述符,但这似乎比这里需要的要复杂得多
  2. 在一个工厂函数中使用闭包来处理 foo,但我觉得嵌套的闭包通常是丑陋且混乱的对象替代品
  3. Foo 实例包装在一个方法中,这个方法将它的 self 传递给 Foo 实例,基本上就是一个装饰器,这实际上是我添加到 Baz 的内容,但这似乎多余,基本上只是比第二种方法更复杂的做法

有没有比这些更好的方法来实现我想要的,还是我应该咬咬牙,使用某种闭包工厂模式呢?

2 个回答

1

你遇到的问题是,你的对象没有被当作Baz类的方法来使用。这是因为它不是一个描述符,而普通函数就是描述符

你可以通过在你的Foo类中添加一个简单的__get__方法来解决这个问题,这样当它作为描述符被访问时,就会变成一个方法:

import types

class Foo(object):
    # your other stuff here

    def __get__(self, obj, objtype=None):
        if obj is None:
            return self # unbound
        else:
            return types.MethodType(self, obj) # bound to obj
6

一种实现这个功能的方法是将可调用的对象(也就是可以被调用的东西)作为未绑定的方法附加到类上。这个方法构造器可以处理各种可调用的对象(也就是说,任何有 __call__() 方法的类的实例),不仅仅是函数。

from types import MethodType

class Foo(object):
    def __init__(self, name, val):
        self.val = val
        self.__name__ = name + '_foo'
        self.name = name
    def __call__(self, instance):
        return self.name + str(self.val + instance.val)

class Baz(object):
    def __init__(self, val):
        self.val = val

Baz.biff = MethodType(Foo("biff", 42), None, Baz)

b = Baz(13)
print b.biff()
>>> biff55

在Python 3中,没有所谓的未绑定实例方法(类里只会有普通的函数)。所以你可以把你的 Foo 类做成一个描述符,通过给它一个 __get__() 方法来返回一个绑定的实例方法。(其实,这种方法在Python 2.x中也能用,但上面提到的方法性能会稍微好一些。)

from types import MethodType

class Foo(object):
    def __init__(self, name, val):
        self.name = name
        self.val = val
        self.__name__ = name + '_foo'
        self.name = name
    def __call__(self, instance):
        return self.name + str(self.val + instance.val)
    def __get__(self, instance, owner):
        return MethodType(self, instance) if instance else self
        # Python 2: MethodType(self, instance, owner)

class Baz(object):
    def __init__(self, val):
        self.val = val

Baz.biff = Foo("biff", 42)

b = Baz(13)
print b.biff()
>>> biff55

撰写回答