Philipp Chapkovski
Philipp Chapkovski

Reputation: 2069

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

I use a specialized python module which modifies some of the Django class methods in the runtime (aka monkey-patching). If I need these 'old' versions is it possible to 'come back' to them overriding monkey patching?

Something like importing the initial version of these classes, for example?

Here is an example of how patching was done in the package:

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

Upvotes: 3

Views: 4541

Answers (1)

Martijn Pieters
Martijn Pieters

Reputation: 1124170

It depends on what the patch did. Monkeypatching is nothing special, it's just an assignment of a different object to a name. If nothing else references the old value anymore, then it's gone from Python's memory.

But if the code that patched the name has kept a reference to the original object in the form of a different variable, then the original object is still there to be '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

Here the name target_function in the target.module module namespace was re-bound to point to new_function, but the original object is still available as _original_function in the namespace of the patching code.

If this is done in a function, then the original could be available as a closure too. For your specific example, you can get the original with:

FilterExpression.resolve.__closure__[0].cell_contents

or, if you prefer access by name:

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']

Otherwise, you can tell Python to reload the original module with importlib.reload():

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

This refreshes the module namespace, 'resetting' all global names to what they'd been set to at import time (any additional names are retained).

Note, however, that any code holding a direct reference to the patched object (such as your class object), would not see the updated objects! That's because from target.module import target_function creates a new reference to the target_function object in the current namespace and no amount of reloading of the original target.module module will update any of the other direct references. You'd have to update those other references manually, or reload their namespaces too.

Upvotes: 7

Related Questions