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)
这取决于补丁做了什么。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