【问题标题】:python monkey patching an object's method within a function via a decoratorpython Monkey通过装饰器在函数中修补对象的方法
【发布时间】:2017-04-21 02:33:37
【问题描述】:

我正在尝试学习一些高级装饰器的用法。具体来说,我正在尝试通过函数中的装饰器来修补类的方法。

这是一个基本示例来说明我正在尝试做的事情。我有一个函数something 可以做一些事情;在该函数中有一个类的实例。那个实例我想猴子补丁。

from functools import update_wrapper

class Foobar:
    def get_something(self):
        return "apple"

class FakeFoobar:
    def get_something(self):
        return "orange"

class my_decorator:
    def __init__(self, original_function):
        self._original_function = original_function
        update_wrapper(self, original_function)

    def __call__(self, *args, **kwargs):
        # some magic here?
        return self._original_function(*args, **kwargs)

@my_decorator
def something():
    f = Foobar()
    return f.get_something()

if __name__ == '__main__':
    print(something())

我正在尝试使用Foobar 到FakeFoobar 或猴子补丁Foobar 的get_something 方法对FakeFoobar 的get_something 方法进行一对一替换。

当我运行上面的代码时,我得到以下信息:

>>> something()
'apple'
>>>

我想找到一些方法来扩充Foobar 的get_something 方法,以便我们得到以下输出:

>>> something()
'orange'
>>>

unittests 库中有一个 mock 模块,但是,尚不清楚如何将其用于我的用例。正如mock 库中的许多示例所示,我非常赞同不将参数传递给装饰器或将额外参数传递给something 函数的想法。

我还注意到moto library 正在完成类似于我正在尝试做的事情。我尝试深入研究源代码,但对于我正在尝试做的事情来说似乎相当复杂。

【问题讨论】:

    标签: python mocking monkeypatching


    【解决方案1】:

    更新函数的全局变量dict怎么样?

    from functools import update_wrapper
    
    class Foobar:
        def get_something(self):
            return "apple"
    
    class FakeFoobar:
        def get_something(self):
            return "orange"
    
    class my_decorator:
        def __init__(self, original_function):
            self._original_function = original_function
            update_wrapper(self, original_function)
    
        def __call__(self, *args, **kwargs):
            f = self._original_function
            restore_val = f.func_globals['Foobar']
            f.func_globals['Foobar'] = f.func_globals['FakeFoobar']
            # ^^^^^ This is your magic-line.
            try:
                return f(*args, **kwargs)
            except:
                raise
            finally:
                f.func_globals['Foobar'] = restore_val
    
    @my_decorator
    def something():
        f = Foobar()
        return f.get_something()
    
    if __name__ == '__main__':
        print(something())   #Prints orange
        print(Foobar().get_something()) #Prints apple
    

    【讨论】:

    • 这非常接近。问题是它会更新整个模块的全局变量,因此如果我在 something 函数之外重新实例化 Foobar,我会返回 FakeFoobar,这是不可取的。
    • @user5038859:您可以随时恢复变量,就像我刚刚更新的答案一样。
    • 这就是我要找的,谢谢。在我将此标记为答案之前,请接受我对 python3 支持的编辑。
    猜你喜欢
    • 2017-04-29
    • 1970-01-01
    • 2011-09-12
    • 2017-03-13
    • 2011-12-15
    • 2011-12-01
    • 1970-01-01
    • 1970-01-01
    • 2019-06-24
    相关资源
    最近更新 更多