[英]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
的行为时,您做了两件事:
第一次尝试时,请在模块中使do_something_expensive成为全局名称。 但是,您正在调用a_function
,它不会在模块中查找解析名称,因此它仍引用相同的函数。
在第二个示例中,您更改了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
以将函数作为参数并调用它,而不是尝试潜入并更改难以编码的函数,或者 使用全局变量(这是从其他模块更改模块级别的东西)是一件坏事 ,它导致难以维护的,令人困惑的,不可测试的,不可扩展的代码,其流程很难跟踪。
a_function()
函数中的do_something_expensive
只是模块命名空间中指向函数对象的变量。 当您重新定义模块时,您将在另一个名称空间中进行操作。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.