#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