使用闭包修饰类的方法

2024-09-28 22:43:24 发布

您现在位置:Python中文网/ 问答频道 /正文

我需要生成一个类,该类将模仿另一个类的方法集,并通过代理像后者一样运行。e、 g.如果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

Tags: selfbasefoomaindefbarfnameclass
1条回答
网友
1楼 · 发布于 2024-09-28 22:43:24

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

相关问题 更多 >