我正在寻找将方法调用从对象(包装器)传递到对象的成员变量(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
如果这个呼叫重定向是“快速”(相对于直接呼叫,即不增加太多开销),那将是很好的。
答案 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
之类的操作仍然有效。