Возможно ли вернуть (вернуться к старому методу) патч Python Monkey? - PullRequest
0 голосов
/ 29 июня 2018

Я использую специализированный модуль python, который изменяет некоторые методы класса Django во время выполнения (также называемый monkey-patching). Если мне нужны эти «старые» версии, можно ли «вернуться» к ним, переопределяя исправления обезьян?

Что-то вроде импорта начальной версии этих классов, например?

Вот пример того, как было сделано исправление в пакете:

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

1 Ответ

0 голосов
/ 29 июня 2018

Зависит от того, что сделал патч. 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

Здесь имя target_function в пространстве имен модуля target.module было связано с указанием на 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)

Это обновляет пространство имен модуля, «сбрасывая» все глобальные имена к тому, что они были установлены во время импорта (все дополнительные имена сохраняются).

Обратите внимание, однако, что любой код, содержащий прямую ссылку на пропатченный объект (такой как ваш объект класса), не будет видеть обновленные объекты! Это связано с тем, что from target.module import target_function создает новую ссылку на объект target_function в текущем пространстве имен, и никакая повторная загрузка исходного модуля target.module не обновляет другие прямые ссылки. Вам придется обновить эти другие ссылки вручную или перезагрузить их пространства имен.

...