繁体   English   中英

一只猴子如何在python中修补功能?

[英]How does one monkey patch a function in python?

我在用另一个功能替换另一个模块中的功能时遇到麻烦,这让我发疯。

假设我有一个看起来像这样的模块bar.py:

from a_package.baz import do_something_expensive

def a_function():
    print do_something_expensive()

我还有另一个看起来像这样的模块:

from bar import a_function
a_function()

from a_package.baz import do_something_expensive
do_something_expensive = lambda: 'Something really cheap.'
a_function()

import a_package.baz
a_package.baz.do_something_expensive = lambda: 'Something really cheap.'
a_function()

我希望得到结果:

Something expensive!
Something really cheap.
Something really cheap.

但是我得到了这个:

Something expensive!
Something expensive!
Something expensive!

我究竟做错了什么?

考虑一下Python名称空间的工作原理可能会有所帮助:它们实际上是字典。 因此,当您执行此操作时:

from a_package.baz import do_something_expensive
do_something_expensive = lambda: 'Something really cheap.'

这样想:

do_something_expensive = a_package.baz['do_something_expensive']
do_something_expensive = lambda: 'Something really cheap.'

希望你能明白为什么这不工作,那么:-)一旦导入一个名称命名空间,这个名字在导入的命名空间的值是不相关的。 您仅在本地模块的名称空间或上面的a_package.baz的名称空间中修改do_something_expensive的值。 但是由于bar直接导入do_something_expensive,而不是从模块名称空间引用它,因此需要写入其名称空间:

import bar
bar.do_something_expensive = lambda: 'Something really cheap.'

有一个非常优雅的装饰器: Guido van Rossum:Python-Dev列表:Monkeypatching Idioms

还有dectools程序包,我看到了PyCon 2010,它也可以在这种情况下使用,但是实际上可能是相反的(在方法声明级别进行monpatpatching……不是)

如果您只想为呼叫打补丁,或者保留原始代码,则可以使用https://docs.python.org/3/library/unittest.mock.html#patch (从Python 3.3开始):

with patch('a_package.baz.do_something_expensive', new=lambda: 'Something really cheap.'):
    print do_something_expensive()
    # prints 'Something really cheap.'

print do_something_expensive()
# prints 'Something expensive!'

在第一个片段,你让bar.do_something_expensive参考函数对象a_package.baz.do_something_expensive是指在那一刻。 要真正实现“ monkeypatch”,您需要更改函数本身(您只更改了所指名称); 这是可能的,但是您实际上并不想这样做。

在尝试更改a_function的行为时,您做了两件事:

  1. 第一次尝试时,请在模块中使do_something_expensive成为全局名称。 但是,您正在调用a_function ,它不会在模块中查找解析名称,因此它仍引用相同的函数。

  2. 在第二个示例中,您更改了a_package.baz.do_something_expensive所指的内容,但bar.do_something_expensive并没有bar.do_something_expensive神奇地bar.do_something_expensive 该名称仍指的是在初始化时查找的功能对象。

最简单但不理想的方法是更改bar.py来表示

import a_package.baz

def a_function():
    print a_package.baz.do_something_expensive()

正确的解决方案可能是以下两件事之一:

  • 重新定义a_function以将函数作为参数并调用它,而不是尝试潜入并更改难以编码的函数,或者
  • 存储要在类的实例中使用的函数; 这就是我们在Python中进行可变状态的方式。

使用全局变量(这是从其他模块更改模块级别的东西)是一件坏事 ,它导致难以维护的,令人困惑的,不可测试的,不可扩展的代码,其流程很难跟踪。

a_function()函数中的do_something_expensive只是模块命名空间中指向函数对象的变量。 当您重新定义模块时,您将在另一个名称空间中进行操作。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM