0

我正在尝试修补pandasPanel 的切片 ( __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 实例。我怎样才能使用monkeypatch面板Foo.foo()并使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

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

4

3 回答 3

1

做到这一点的一种方法是创建一个闭包(一个引用非本地或全局名称的函数)。一个简单的闭包:

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

我的系统上没有 pandas,但它与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'])
于 2015-02-27T14:57:57.137 回答
1

您可以使用patch模拟框架来处理您的案例。即使它是为测试而设计的,它的主要工作是在定义的上下文中进行猴子修补。

你的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是方法或对函数的引用时,这将起作用。

于 2015-02-27T13:49:28.780 回答
1

猴子补丁的基础很简单,但它很快就会变得棘手和微妙,特别是如果你的目标是找到一个适用于 Python 2 和 Python 3 的解决方案。

此外,快速破解的解决方案通常不是非常可读/可维护的,除非您设法很好地包装猴子修补逻辑。

这就是为什么我邀请你看看我专门为此目的编写的一个库。它被命名为 Gorilla,你可以在GitHub 上找到它。

简而言之,它提供了一组很酷的功能,它具有广泛的单元测试,并且它带有一个精美的文档,应该涵盖你开始所需的一切。确保还检查常见问题解答!

于 2015-02-27T12:15:00.767 回答