是否可以恢复(返回到旧方法)python 猴子补丁

Phi*_*ski 3 python monkeypatching

我使用一个专门的 python 模块,它在运行时修改一些 Django 类方法(又名猴子修补)。如果我需要这些“旧”版本,是否可以“返回”它们并覆盖猴子补丁?

例如,导入这些类的初始版本之类的东西?

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

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
Run Code Online (Sandbox Code Playgroud)

Mar*_*ers 7

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

但是,如果修补名称的代码以不同变量的形式保留了对原始对象的引用,那么原始对象仍然可以“恢复”:

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
Run Code Online (Sandbox Code Playgroud)

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

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

FilterExpression.resolve.__closure__[0].cell_contents
Run Code Online (Sandbox Code Playgroud)

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

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']
Run Code Online (Sandbox Code Playgroud)

否则,您可以使用以下命令告诉 Python重新加载原始模块importlib.reload()

import target.module
importlib.reload(target.module)
Run Code Online (Sandbox Code Playgroud)

这会刷新模块名称空间,将所有全局名称“重置”为导入时设置的名称(保留任何其他名称)。

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