Как одна обезьяна исправляет функцию в python?
у меня возникли проблемы с заменой функции из другого модуля с другой функцией, и это сводит меня с ума.
допустим, у меня есть модуль bar.py это выглядит так:
from a_package.baz import do_something_expensive
def a_function():
print do_something_expensive()
и у меня есть еще один модуль, который выглядит так:
from bar import a_function
a_function()
from a_package.baz import do_something_expensive
do_something_expensive = lambda: 'Something really cheap.'
a_function()
import a_package.baz
a_package.baz.do_something_expensive = lambda: 'Something really cheap.'
a_function()
Я ожидал бы получить результаты:
Something expensive!
Something really cheap.
Something really cheap.
но вместо этого я получаю это:
Something expensive!
Something expensive!
Something expensive!
что я делаю не так?
5 ответов:
это может помочь подумать о том, как работают пространства имен Python: они по существу словари. Поэтому, когда вы делаете это:
from a_package.baz import do_something_expensive do_something_expensive = lambda: 'Something really cheap.'
подумайте об этом так:
do_something_expensive = a_package.baz['do_something_expensive'] do_something_expensive = lambda: 'Something really cheap.'
надеюсь, вы можете понять, почему это не работает: -) как только вы импортируете имя в пространство имен, значение имени в пространстве имен, которое вы импортировали С не имеет значения. Вы только изменяете значение do_something_expensive в пространстве имен локального модуля или в a_package.бэз по пространства имен выше. Но поскольку bar импортирует do_something_expensive напрямую, а не ссылается на него из пространства имен модуля, вам нужно написать в его пространство имен:
import bar bar.do_something_expensive = lambda: 'Something really cheap.'
есть действительно элегантный декоратор для этого:Guido van Rossum: Python-Dev list: Monkeypatching Idioms.
есть еще dectools пакет, который я видел в PyCon 2010, который также может быть использован в этом контексте, но это может пойти по-другому (monkeypatching на декларативном уровне метода... где тебя нет)
в первом фрагменте, вы делаете
bar.do_something_expensive
обратитесь к объекту функции, которыйa_package.baz.do_something_expensive
относится к этому моменту. Чтобы действительно "обезьяна", что вам нужно будет изменить саму функцию (вы только меняете то, что имена ссылаются); это возможно, но вы на самом деле не хотите этого делать.в ваших попытках изменить поведение
a_function
- вы сделали две вещи:
С первой попытки вы делаете do_something_expensive глобальным имя в вашем модуле. Тем не менее, вы звоните
a_function
, который не выглядит в вашем модуле для разрешения имен, поэтому он по-прежнему относится к той же функции.во втором примере вы меняете то, что
a_package.baz.do_something_expensive
относится, ноbar.do_something_expensive
магически не привязан к нему. Это имя по-прежнему относится к объекту функции, который он искал, когда он был инициализирован.самый простой, но далеко не идеальным подходом было бы изменить
bar.py
to скажиimport a_package.baz def a_function(): print a_package.baz.do_something_expensive()
правильное решение, вероятно, одна из двух вещей:
- изменить
a_function
чтобы взять функцию в качестве аргумента и вызвать ее, а не пытаться проникнуть и изменить, на какую функцию трудно ссылаться, или- сохраните функцию, которая будет использоваться в экземпляре класса; это то, как мы делаем изменяемое состояние в Python.
использование глобалов (это то, что меняет материал уровня модуля из других модулей есть) это плохо это приводит к недостижимому, запутанному, непроверяемому, не поддающемуся масштабированию коду, поток которого трудно отследить.
Если вы хотите только исправить его для вашего вызова и в противном случае оставить исходный код вы можете использовать https://docs.python.org/3/library/unittest.mock.html#patch (начиная с Python 3.3):
with patch('a_package.baz.do_something_expensive', new=lambda: 'Something really cheap.'): print do_something_expensive() # prints 'Something really cheap.' print do_something_expensive() # prints 'Something expensive!'