2013-10-26 33 views
3

我需要生成一个类来模仿另一个类的方法集,并通过代理来执行后者的行为。例如如果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

'proxy_func()'中的'fname'是* closure *;直到你调用'proxy_func()',它才被查找,此时它的值仍然绑定到最后一个值(在这种情况下为'foo2'),**不是**当你创建嵌套功能。 –

+0

解决方法是在某处创建局部变量以将值绑定到循环中;一个单独的工厂函数可以做到这一点,或者通过给'proxy_func()'一个关键字参数来绑定'fname'作为函数对象的默认值。 –

+0

@MartijnPieters我做到了。但是它失败了,因为'AttributeError:'Base'对象在'd .__ bar()#d._proxy .__ bar()'上没有属性'__bar''。 :'( – thefourtheye

回答

0

函数创建闭包,循环没有。变量名称fnameproxyfy中的局部变量。嵌套函数proxy_func引用此局部变量。但在当时,当嵌套函数被调用时,for-loop

for _, func in inspect.getmembers(target, predicate=inspect.ismethod): 

已完成,局部变量fname在循环,这恰好是'foo2'的末尾引用了其最后的值。 因此,无论您拨打什么方式,每个proxy_func最终都会致电foo2

要将fname的不同值绑定到每个proxy_func,可以使用一个新的关键字参数bname和一个默认值。默认值绑定到定义时间上的函数,而不是函数在运行时。因此,如果使用

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() 
+0

谢谢!你的第一句话总结一切。此外,您还对“bname”和“fname”进行了区分,这在使用损坏的方法进行播放时非常重要。 – user2221662