Исправление обезьян в Python с использованием списков

#python #monkeypatching

#python #исправление обезьян

Вопрос:

Мне нужно переопределить функциональность в нескольких библиотеках, и я хотел бы программно исправлять функции путем итерации по списку, который я указываю заранее.

Исправление работает, если я настроил свой код следующим образом:

 def monkey_patch(method):
    def patch(*args, **kwargs):
        print('Patch!')
        return method(*args, **kwargs)
    return patch

def func_a(text):
    print(f'You called func_a with: {text}')
    
func_a('Hello') #  You called func_a with: Hello

original = func_a
func_a = monkey_patch(func_a)
func_a('Hello again') #  Patch!
                      #  You called func_a with: Hello again

func_a = original
func_a('Hello one last time') #  You called func_a with: Hello one last time
  

Однако попытка сделать это с помощью списка завершается неудачей, потому что назначение думает, что я пытаюсь назначить функцию контейнеру вместо функции, на которую указывают:

 patch_list = [(func_a, monkey_patch)]
unpatch_list = []

for patch in patch_list:
    original = patch[0]
    patch[0] = patch[1](patch[0]) #  TypeError: 'tuple' object does not support item assignment
    unpatch_list.append((patch[0], original)) #  Store the original function so the patch can be removed later
    
for patch in unpatch_list:
    patch[0] = patch[1] #  TypeError: 'tuple' object does not support item assignment
  

Есть ли способ «разыменования» элементов в кортеже, чтобы позволить методологии списка работать?

Комментарии:

1. Я думаю, вы в корне неправильно понимаете семантику присваивания. Обратите внимание, то, что вы делаете, на самом деле не является исправлением обезьян. Вы украшаете эти функции. Чтобы сделать это динамически, кажется, вы хотите сделать это внутри самого модуля. Таким образом, одним из подходов было бы globals() прямое изменение.

2. «разыменование» не имеет смысла в Python, в Python нет указателей . original ссылается на фактический объект функции, который вас интересует. Он уже «разыменован». Обратите внимание, вы используете tuple объекты, которые являются неизменяемыми . Но даже если бы вы использовали объекты списка, это на самом деле не сделало бы того, что вы пытаетесь сделать

3. Спасибо @juanpa.arrivillaga! Globals() — это именно то, что мне было нужно, теперь оно отлично работает.

Ответ №1:

Благодаря juanpa.arrivillaga, вот способ сделать это как для декораторов, так и для обычных исправлений обезьян с использованием globals():

 def patch_a(method):
    def patch(*args, **kwargs):
        print('Patch 1!')
        return method(*args, *kwargs)
    return patch

def patch_b(text):
    print(f'Patch 2 called with: {text}')

def func_a(text):
    print(f'You called func_a with: {text}')
    
def func_b(text):
    print(f'You called func_b with: {text}')

patch_list = [
    ('func_a', patch_a, True),
    ('func_b', patch_b, False)
             ]
unpatch_list = []

func_a('Hello')  # You called func_a with: Hello
func_b('Hello again')  # You called func_b with: Hello again

for patch in patch_list:
    unpatch_list.append((patch[0], globals()[patch[0]]))
    
    if patch[2]:
        globals()[patch[0]] = patch[1](globals()[patch[0]])
    else:
        globals()[patch[0]] = patch[1]

func_a('Hello')  # Patch 1!
                 # You called func_a with: Hello
func_b('Hello again')  # Patch 2 called with: Hello again

for patch in unpatch_list:
    globals()[patch[0]] = patch[1]

func_a('Hello')  # You called func_a with: Hello
func_b('Hello again')  # You called func_b with: Hello again