【发布时间】:2013-10-26 13:40:39
【问题描述】:
我需要生成一个类,该类将模仿另一个类的方法集,并通过代理像后者一样工作。例如如果Base 是要模仿的类,Deleguate 是需要充当Base 的类,那么:
b = Base(args)
b.any_function()
严格等价于
d = Deleguate(b)
d.any_function()
如果Deleguate 使用了Base 中已经存在的函数,它不会被覆盖。这是您对继承和方法覆盖所期望的那种行为。在我正在处理的项目的上下文中,继承不是一个选项(除其他限制外,我无权访问工厂代码)。这就是让事情变得复杂的原因。
因此我决定编写一个“代理”装饰器:
import inspect
def proxy(bridge, target):
def proxyfy(cls):
for _, func in inspect.getmembers(target, predicate=inspect.ismethod):
fname = func.__name__
if fname in cls.__dict__:
print 'ignoring %s.%s' % (cls, fname)
continue
print 'adding %s.%s' % (cls, fname)
def proxy_func(self, *args, **kwargs):
print 'calling %s.%s.%s' % (cls, bridge, fname)
bridge_member = getattr(self, bridge)
return getattr(bridge_member, fname)(*args, **kwargs)
setattr(cls, fname, proxy_func)
return cls
return proxyfy
class Base(object):
def __init__(self, i):
self._i = i
def __bar(self):
print 0
def foo(self):
print self._i
def foo2(self):
print 2 * self._i
@proxy('_proxy', Base)
class Deleguate(object):
def __init__(self, base):
self._proxy = base
def foo2(self):
print 4 * self._proxy._i
d = Deleguate(Base(1))
d.__bar() # d._proxy.__bar()
d.foo() # d._proxy.foo()
d.foo2() # d.foo2()
我得到以下输出:
adding <class '__main__.Deleguate'>.__bar
ignoring <class '__main__.Deleguate'>.__init__
adding <class '__main__.Deleguate'>.foo
ignoring <class '__main__.Deleguate'>.foo2
calling <class '__main__.Deleguate'>._proxy.foo2
2
calling <class '__main__.Deleguate'>._proxy.foo2
2
4
我认为setattr(cls, fname, proxy_func) 会分配一个新的闭包,但是在每个循环步骤中参数都会被覆盖,并且只保留最后一个函数foo2 的参数。因此调用Deleguate 的任何“生成”函数使用foo2 参数...
为什么会覆盖闭包参数?有没有办法生成那种代理代码?预期的输出是:
adding <class '__main__.Deleguate'>.__bar
ignoring <class '__main__.Deleguate'>.__init__
adding <class '__main__.Deleguate'>.foo
ignoring <class '__main__.Deleguate'>.foo2
calling <class '__main__.Deleguate'>._proxy.__bar
0
calling <class '__main__.Deleguate'>._proxy.foo
1
4
【问题讨论】:
-
fnameinproxy_func()是一个闭包;在您调用proxy_func()之前不会查找它,此时它的值仍然绑定到最后一个值(在本例中为foo2),不是它在创建时绑定的值嵌套函数。 -
解决方法是在某处创建一个局部变量,将值绑定到循环中;一个单独的工厂函数可以做到这一点,或者通过给
proxy_func()一个关键字参数,将fname作为默认值绑定到函数对象。 -
@MartijnPieters 我做到了。但它在
d.__bar() # d._proxy.__bar()上使用AttributeError: 'Base' object has no attribute '__bar'失败。 :'( -
@MartijnPieters 自从我开始研究这个已经半小时了。 :( 我改变了这样的签名
def proxy_func(self, fname=fname, *args, **kwargs):. -
@thefourtheye:那是因为双下划线名称被弄乱了;见docs.python.org/2/reference/expressions.html#atom-identifiers