我在用另一个函数替换不同模块中的函数时遇到了麻烦,这让我发疯。

假设我有一个模块 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.'

希望你能明白为什么这不工作,那么:-)一旦导入一个名称命名空间,名称的命名空间中的价值,你进口的的是无关紧要的。你只是修改do_something_expensive的本地模块的名称空间,或者在a_package.baz的命名空间的价值,以上。但由于巴进口直接do_something_expensive,而不是从模块命名空间引用它,你需要写它的命名空间:

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

其他提示

有这个一个非常优雅的装饰:吉多·范罗苏姆: Python的开发列表:。的Monkeypatching成语

另外还有 dectools 包,我所看见的PYCON 2010,其或许可以在这种情况下被使用过,但实际上可能会走另一条路(在方法声明级别的Monkeypatching ...这里你不是)

如果您希望只打补丁您的垂询和否则保留原来的代码,你可以使用的 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_expense 设置为模块中的全局名称。然而,你正在打电话 a_function, ,它不会在您的模块中查找来解析名称,因此它仍然引用相同的函数。

  2. 在第二个例子中你改变了什么 a_package.baz.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 中处理可变状态的方式。

使用全局变量(这就是从其他模块更改模块级别的内容)是 坏事 这会导致不可维护、令人困惑、不可测试、不可扩展的代码,并且其流程难以跟踪。

do_something_expensivea_function()功能仅仅是模块指向一个功能对象的名称空间内的变量。当重新定义模块你正在做它在不同的命名空间。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top