是否有可能恢复(return 到旧方法)python 猴子补丁

is it possible to revert (return to old method) python monkey patch

我使用了一个专门的 python 模块,它在运行时修改了一些 Django class 方法(又名猴子修补)。如果我需要这些 'old' 版本,是否可以 'come back' 覆盖猴子补丁?

例如导入这些 class 的初始版本?

以下是如何在包中完成修补的示例:

from django.template.base import FilterExpression

def patch_filter_expression():
    original_resolve = FilterExpression.resolve
    def resolve(self, context, ignore_failures=False):
        return original_resolve(self, context, ignore_failures=False)

    FilterExpression.resolve = resolve

这取决于补丁做了什么。 Monkeypatching 没什么特别的,它只是将不同的对象分配给一个名称。如果没有其他东西引用旧值,那么它就从 Python 的内存中消失了。

但是如果修补名称的代码以不同变量的形式保留了对原始对象的引用,那么原始对象仍然存在 'restored':

import target.module

_original_function = target.module.target_function

def new_function(*args, **kwargs):
    result = _original_function(*args, **kwargs)
    return result * 5

target.module.target_function = new_function

此处 target.module 模块命名空间中的名称 target_function 被重新绑定为指向 new_function,但原始对象仍然可用 _original_function补丁代码的命名空间。

如果这是在一个函数中完成的,那么原件也可以作为 闭包 使用。对于您的具体示例,您可以通过以下方式获取原始文件:

FilterExpression.resolve.__closure__[0].cell_contents

或者,如果您更喜欢按名称访问:

def closure_mapping(func):
    closures, names = func.__closure__, func.__code__.co_freevars
    return {n: c.cell_contents for n, c in zip(names, closures)}

original_resolve = closure_mapping(FilterExpression.resolve)['original_resolve']

否则,您可以告诉 Python 重新加载 原始模块 importlib.reload():

import target.module
importlib.reload(target.module)

这会刷新模块命名空间,'resetting' 所有全局名称都将恢复为导入时设置的名称(保留任何其他名称)。

但是请注意,任何直接引用 已修补 对象(例如您的 class 对象)的代码都不会看到更新的对象!这是因为 from target.module import target_function 在当前命名空间中创建了对 target_function 对象的新引用,并且重新加载原始 target.module 模块不会更新任何其他直接引用。您必须手动更新那些其他引用,或者也重新加载它们的命名空间。