使用实例方法的Monkeypatch

时间:2015-02-05 23:16:31

标签: python pandas partials monkeypatching functools

我正在尝试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

是的,我知道这是不好的做法,但这只是暂时的解决方法。

3 个答案:

答案 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'])