我正在尝试monkeypatch pandas
Panel的切片(__getitem__
)。这很简单,基本功能就是foo。
from pandas import Panel
Panel.__getitem__ = ORIGINAL_getitem
def newgetitem(panel, *args, **kwargs):
""" Append a string to return of panel.__getitem__"""
out = super(Panel, panel).__getitem__(*args, **kwargs)
return out+'custom stuff added'
Panel.__getitem__ = newgetitem
ORIGINAL_getitem
存储原始Panel方法的位置。我试图扩展到foo()
不是函数的情况,而是对象的实例方法Foo
。例如:
class Foo:
name = 'some name'
def newgetitem(self, panel, *args, **kwargs):
""" Append a string to return of panel.__getitem__,
but take attributes from self, like self.name
"""
out = super(Panel, panel).__getitem__(*args, **kwargs)
return out+'custom stuff added including name' + self.name
Foo.foo()
必须访问属性self.name
。因此,除了Panel之外,monkeypatched函数还需要以某种方式引用Foo实例。如何使用Foo.foo()
对面板进行monkepatch并使self.name可访问?
猴子修补功能之间的切换发生在另一种方法Foo.set_backend()
class Foo:
name = 'some name'
def foo(self):
return 'bar, called by %s' % self.name
def set_backend(self, backend):
""" Swap between new or original slicing."""
if backend != 'pandas':
Panel.__getitem__ = newgetitem
else:
Panel.__getitem__ = ORIGINAL_getitem
我真正需要的是newgetitem
维持对self
的引用。
到目前为止,我已尝试将newgetitem()
作为纯函数,并使用部分函数将引用传递给self。这不起作用。类似的东西:
import functools
def newgetitem(foo_instance, panel, *args, **kwargs):
....
class Foo:
...
def set_backend(self, backend):
""" Swap between new or original slicing."""
if backend != 'pandas':
partialfcn = functools.partial(newgetitem, self)
Panel.__getitem__ = partialfcn
else:
Panel.__getitem__ = ORIGINAL_getitem
但这不起作用。传递了对self的引用,但是不能从调用Panel访问。那就是:
panel['50']
传递对Foo
的引用,而不是Panel
。
是的,我知道这是不好的做法,但这只是暂时的解决方法。
答案 0 :(得分:1)
猴子补丁的基础很简单,但很快就会变得棘手和微妙,特别是如果你的目标是找到一个适用于Python 2和Python 3的解决方案。
此外,快速入侵的解决方案通常不是非常易读/可维护,除非您设法很好地包装猴子修补逻辑。
这就是为什么我邀请你看一下我为此专门编写的图书馆。它被命名为Gorilla,您可以在GitHub找到它。
简而言之,它提供了一组很酷的功能,它具有广泛的单元测试,并附带a fancy doc,它应涵盖入门所需的一切。请务必查看常见问题解答!
答案 1 :(得分:1)
您可以使用patch
from mock framework来处理您的案件。即使它是专为测试而设计的,它的主要工作是在定义的上下文中进行猴子修补。
您的set_backend()
方法可以是:
def set_backend(self, backend):
if backend != 'pandas' and self._patched_get_item is None:
self._patched_get_item = patch("pandas.Panel.__getitem__", autospec=True, side_effect=self._getitem)
self._patched_get_item.start()
elif backend == 'pandas' and self._patched_get_item is not None:
self._patched_get_item.stop()
self._patched_get_item = None
当self._getitem
是方法或对函数的引用时,这将起作用。
答案 2 :(得分:1)
执行此操作的一种方法是创建一个闭包(一个引用除locals或globals之外的名称的函数)。一个简单的闭包:
def g(x):
def f():
"""f has no global or local reference to x, but can refer to the locals of the
context it was created in (also known as nonlocals)."""
return x
return f
func = g(1)
assert func() == 1
我的系统上没有大熊猫,但它与dict
大致相同。
class MyDict(dict):
pass
d = MyDict(a=1, b=2)
assert d['a'] == 1
class Foo:
name = 'name'
def create_getitem(fooself, cls):
def getitem(self, *args, **kwargs):
out = super(cls, self).__getitem__(*args, **kwargs)
return out, 'custom', fooself.name
# Above references fooself, a name that is not defined locally in the
# function, but as part of the scope the function was created in.
return getitem
MyDict.__getitem__ = Foo().create_getitem(MyDict)
assert d['a'] == (1, 'custom', Foo.name)
print(d['a'])