【问题标题】:Using closure to decorate a class's methods [duplicate]使用闭包来装饰类的方法[重复]
【发布时间】: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

【问题讨论】:

  • fname in proxy_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

标签: python python-decorators


【解决方案1】:

函数创建闭包,循环不会。变量名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()

【讨论】:

  • 谢谢!你的第一句话总结了一切。另外,您还区分了 bnamefname,这在使用损坏的方法时很重要。
猜你喜欢
  • 2013-09-05
  • 2012-02-09
  • 2017-07-28
  • 2020-12-03
  • 2014-01-14
  • 2011-08-25
  • 2020-01-11
  • 2019-06-10
相关资源
最近更新 更多