Python中的代理对象

2024-04-29 13:24:23 发布

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

我正在寻找将方法调用从对象(包装器)传递到对象(包装器)的成员变量的方法。可能有许多方法需要外部化,因此在将方法添加到wrappee时不更改包装器的接口的方法将非常有用。

class Wrapper(object)
  def __init__(self, wrappee):
    self.wrappee = wrappee

  def foo(self):
    return 42

class Wrappee(object):
  def bar(self):
    return 12

o2 = Wrappee()
o1 = Wrapper(o2)

o1.foo() # -> 42
o1.bar() # -> 12
o1.<any new function in Wrappee>() # call directed to this new function 

如果这个调用重定向是“快速的”(相对于直接调用,即不增加太多开销),那就太好了。


Tags: 对象方法selfnewreturnobjectfoodef
2条回答

如果您真的需要加快速度,那么最快的方法是在初始化时自己进行monkeypatch:

def __init__(self, wrappee):
    for name, value in inspect.getmembers(wrappee, callable):
        if not hasattr(self, name):
            setattr(self, name, value)

这将为您的Wrapper实例提供常规数据属性,这些属性的值是Wrappee的绑定方法。那应该非常快。它是?

class WrapperA(object):
    def __init__(self, wrappee):
        self.wrappee = wrappee
        for name, value in inspect.getmembers(wrappee, callable):
            if not hasattr(self, name):
                setattr(self, name, value)

class WrapperB(object):
    def __init__(self, wrappee):
        self.wrappee = wrappee
    def __getattr__(self, name):
        return getattr(self.wrappee, name)

In [1]: %run wrapper
In [2]: o2 = Wrappee()
In [3]: o1a = WrapperA(o2)
In [4]: o1b = WrapperB(o2)
In [5]: %timeit o2.bar()
10000000 loops, best of 3: 154 ns per loop
In [6]: %timeit o1a.bar()
10000000 loops, best of 3: 159 ns per loop
In [7]: %timeit o1b.bar()
1000000 loops, best of 3: 879 ns per loop
In [8]: %timeit o1b.wrapper.bar()
1000000 loops, best of 3: 220 ns per loop

所以,复制绑定的方法有3%的成本(不知道为什么它甚至有那么多…)。任何比这更动态的操作都必须从self.wrapper中提取属性,这至少有66%的开销。通常的__getattr__解决方案有471%的开销(向它添加不必要的额外内容只会使它变慢)。

所以,这听起来像是一个开放和封闭的方法黑客的胜利,对吧?

不一定。471%的开销仍然只有700纳秒。这真的会改变你的代码吗?可能不会,除非它是在一个紧密的循环中使用的,在这种情况下,您几乎肯定要将方法复制到一个局部变量。

而且这次黑客攻击有很多缺点。这不是“一个显而易见的方法”。对于不在实例dict上查找的特殊方法,它将不起作用。它将静态地从o2中提取属性,因此,如果以后创建任何新的属性,o1将不会对它们进行代理(尝试用这种方式构建动态代理链…)。如果你有很多代理,那会浪费很多内存。Python 2.x和3.x(甚至在2.x和3.x系列中,如果您依赖于inspect)的话,两者之间也略有不同,而__getattr__从2.3到现在(以及在其他Python实现中)都非常小心地保持不变。等等。

如果您真的需要这个速度,您可能需要考虑一个混合方法:缓存代理方法的__getattr__方法。您甚至可以分两个阶段执行:一个是调用一次的,将未绑定的方法缓存在类属性中并动态绑定;如果随后重复调用,则将绑定的方法缓存在实例属性中。

有点优雅的解决方案是在包装类上创建一个“属性代理”:

class Wrapper(object):
    def __init__(self, wrappee):
        self.wrappee = wrappee

    def foo(self):
        print 'foo'

    def __getattr__(self, attr):
        return getattr(self.wrappee, attr)


class Wrappee(object):
    def bar(self):
        print 'bar'

o2 = Wrappee()
o1 = Wrapper(o2)

o1.foo()
o1.bar()

所有的魔术都发生在Wrapper类的__getattr__方法上,该方法将尝试访问Wrapper实例上的方法或属性,如果它不存在,它将尝试使用包装的方法或属性。

如果试图访问两个类中都不存在的属性,则会得到以下结果:

o2.not_valid
Traceback (most recent call last):
  File "so.py", line 26, in <module>
    o2.not_valid
  File "so.py", line 15, in __getattr__
    raise e
AttributeError: 'Wrappee' object has no attribute 'not_valid'

相关问题 更多 >