我需要生成一个类,它将模仿另一个类的方法集,并通过代理表现得像后者。例如如果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
答案 0 :(得分:0)
函数创建闭包,循环不。变量名称fname
是proxyfy
中的局部变量。嵌套函数proxy_func
引用此局部变量。但是在调用嵌套函数时,for-loop
for _, func in inspect.getmembers(target, predicate=inspect.ismethod):
已完成,并且局部变量fname
在循环结束时引用其最后一个值,恰好是'foo2'
。
因此,无论您拨打什么方法,每个proxy_func
最终都会调用foo2
。
要将fname
的不同值绑定到每个proxy_func
,您可以使用带有默认值的新关键字参数bname
。默认值绑定到 definition-time 处的函数,而不是运行中的函数。所以如果使用
for bname, func in inspect.getmembers(target, predicate=inspect.ismethod):
并使用此bname
作为默认值:
def proxy_func(self, bname=bname, *args, **kwargs):
然后每个proxy_func
都会调用相应的bname
。
因此,只需对代码进行最少的更改,就可以将一个带有默认值的关键字参数添加到proxy_func
,以便记住当前的方法名称:
def proxy(bridge, target):
def proxyfy(cls):
for bname, 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, bname=bname, *args, **kwargs):
print 'calling %s.%s.%s' % (cls, bridge, bname)
bridge_member = getattr(self, bridge)
return getattr(bridge_member, bname)(*args, **kwargs)
setattr(cls, fname, proxy_func)
return cls
return proxyfy
但是,我认为使用__getattr__
可能会更容易:
def proxy(bridge):
def proxyfy(cls):
def __getattr__(self, attr):
target = getattr(self, bridge)
if attr.startswith('__') and not attr.endswith('__'):
# unmangle
attr = '_{}{}'.format(type(target).__name__, attr)
return getattr(target, attr)
setattr(cls, '__getattr__', __getattr__)
return cls
return proxyfy
这是一个可运行的例子:
import inspect
def proxy(bridge, target):
def proxyfy(cls):
for bname, 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, bname=bname, *args, **kwargs):
print 'calling %s.%s.%s' % (cls, bridge, bname)
bridge_member = getattr(self, bridge)
return getattr(bridge_member, bname)(*args, **kwargs)
setattr(cls, fname, proxy_func)
return cls
return proxyfy
def proxy(bridge):
def proxyfy(cls):
def __getattr__(self, attr):
target = getattr(self, bridge)
if attr.startswith('__') and not attr.endswith('__'):
# unmangle
attr = '_{}{}'.format(type(target).__name__, attr)
return getattr(target, attr)
setattr(cls, '__getattr__', __getattr__)
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)
@proxy('_proxy')
class Delegate(object):
def __init__(self, base):
self._proxy = base
def foo2(self):
print 4 * self._proxy._i
d = Delegate(Base(1))
d.__bar() # d._proxy.__bar()
d.foo() # d._proxy.foo()
d.foo2() # d.foo2()