【问题标题】:Monkeypatch with instance method带有实例方法的 Monkeypatch
【发布时间】:2015-02-05 23:16:30
【问题描述】:

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

WhereORIGINAL_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() 进行蒙面补丁面板并使 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。

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

【问题讨论】:

    标签: python pandas partials monkeypatching functools


    【解决方案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】:

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

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

      【讨论】:

        【解决方案3】:

        monkey patching 的基础知识很简单,但很快就会变得棘手和微妙,尤其是当您的目标是找到一种适用于 Python 2 和 Python 3 的解决方案时。

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

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

        简而言之,它提供了一组很酷的功能,它具有广泛的单元测试,并且它附带了a fancy doc,它应该涵盖了您入门所需的一切。请务必查看常见问题解答!

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-04-07
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多