动态将可调用对象添加为类的实例“方法”
我实现了一个元类,这个元类可以拆解用它创建的类的属性,并根据这些属性的数据构建方法,然后把这些动态创建的方法直接附加到类对象上(这个类主要是为了方便定义网页表单对象,以便在网页测试框架中使用)。这个功能一直运行得很好,但现在我需要添加一种更复杂的方法。为了保持代码的整洁,我把它实现成了一个可调用的类。不幸的是,当我尝试在一个实例上调用这个可调用的类时,它被当作类属性对待,而不是实例方法,调用时只接收到它自己的 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'
我考虑过:
- 使用描述符,但这似乎比这里需要的要复杂得多
- 在一个工厂函数中使用闭包来处理
foo
,但我觉得嵌套的闭包通常是丑陋且混乱的对象替代品 - 把
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