Python中的代理对象

时间:2014-09-29 02:45:52

标签: python

我正在寻找将方法调用从对象(包装器)传递到对象的成员变量(wrappee)的方法。可能有许多方法需要外部化,因此在向wrapee添加方法时,如果不更改包装器的界面,这样做是有帮助的。

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 

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

3 个答案:

答案 0 :(得分:8)

如果真的需要快速,那么最快的选择就是在初始化时自行编写代码:

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纳秒。这真的会对你的代码产生影响吗?可能不会,除非它被用在一个紧密的循环中 - 在这种情况下,你几乎肯定会想要将方法复制到局部变量。

这个黑客有很多缺点。它并不是一种显而易见的方法。#34;对于在实例字典中没有查找的特殊方法,它不会工作。它将属性从o2静态拉出,因此如果您稍后创建任何新属性,o1将无法代理它们(尝试以这种方式构建动态代理链... )。如果你有很多代理,那就浪费了很多内存。它在Python 2.x和3.x之间略有不同(甚至在2.x和3.x系列中,如果你依赖inspect),而__getattr__则非常谨慎从2.3到现在保持不变(以及在替代Python实现中)。等等。

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

答案 1 :(得分:7)

一个稍微优雅的解决方案是创建一个&#34;属性代理&#34;在包装类上:

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

所有的魔法发生在__getattr__类的Wrapper方法上,它将尝试访问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'

答案 2 :(得分:2)

这是另一种猴子补丁方法。这个代码直接将方法复制到Wrapper类中,而不是将方法复制到创建的包装对象中。这一方法的主要优点是,所有__add__之类的特殊方法都可以使用。

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

    def foo(self):
        print('foo')


def proxy_wrap(attr):
    "This method creates a proxy method that calls the wrappee's method."
    def f(self, *args):
        return getattr(self.wrappee, attr)(*args)
    return f

# Don't overwrite any attributes already present
EXCLUDE = set(dir(Wrapper))

# Watch out for this one...
EXCLUDE.add('__class__')

for (attr, value) in inspect.getmembers(Wrappee, callable):
    if attr not in EXCLUDE:
        setattr(Wrapper, attr, proxy_wrap(attr))

我用它来包装numpy数组。将Wrappee设置为np.ndarray

import numpy as np

Wrappee = np.ndarray

# [The block I wrote above]

wrapped = Wrapper(np.arange(10))

wrapped + 1之类的操作仍然有效。