代码之家  ›  专栏  ›  技术社区  ›  guidoism

一只猴子如何在python中修补函数?

  •  69
  • guidoism  · 技术社区  · 15 年前

    我在用另一个功能替换另一个模块中的一个功能时遇到问题,这让我抓狂。

    假设我有一个module 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!
    

    我做错什么了?

    5 回复  |  直到 8 年前
        1
  •  79
  •   Nicholas Riley    15 年前

    考虑一下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.'
    

    希望您能够了解为什么这样做不起作用,然后:-)一旦将名称导入到命名空间中,则导入的命名空间中名称的值 是无关紧要的。您只需在本地模块的名称空间或上面的一个_package.baz的名称空间中修改do_something_昂贵的值。但是,由于bar导入不直接引用昂贵的内容,而不是从模块名称空间引用它,因此需要写入其名称空间:

    import bar
    bar.do_something_expensive = lambda: 'Something really cheap.'
    
        2
  •  20
  •   RyanWilcox    15 年前

    这里有一个非常优雅的装饰: Guido van Rossum: Python-Dev list: Monkeypatching Idioms .

    还有 dectools 包,我看到了一个pycon 2010,它可能也可以在这个上下文中使用,但实际上可能会走另一条路(在方法声明级别的monkeypatching…你不在的地方)

        3
  •  4
  •   Risadinha    10 年前

    如果您只想为您的调用修补它,或者保留您可以使用的原始代码 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!'
    
        4
  •  3
  •   Mike Graham    15 年前

    在第一个片段中,您将 bar.do_something_expensive 引用的函数对象 a_package.baz.do_something_expensive 指当时。为了实现真正的“monkeypatch”,您需要更改函数本身(您只是更改名称所指的内容);这是可能的,但实际上您并不想这样做。

    在你试图改变 a_function ,你做了两件事:

    1. 在第一次尝试中,您将do_something_昂贵的全局名称放在模块中。不过,你打电话来 A-函数 ,它不会在模块中查找以解析名称,因此它仍然引用相同的函数。

    2. 在第二个例子中,你改变了 A_package.baz.do_昂贵的东西 指,但是 酒吧。有什么贵的吗 不是魔术般地绑在上面。这个名称仍然是指它在初始化时查找的函数对象。

    最简单但远非理想的方法是改变 bar.py

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

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

    • 重新定义 A-函数 把一个函数作为参数并调用它,而不是试图潜入并更改它所引用的硬编码函数,或者
    • 存储要在类的实例中使用的函数;这是我们在python中执行可变状态的方式。

    使用globals(这就是从其他模块更改模块级的内容)是 坏事 这会导致无法维护、混乱、不可测试、不可扩展的代码,这些代码的流程很难跟踪。

        5
  •  0
  •   vishes_shell hendrixski    8 年前

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