Вопрос

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

Допустим, у меня есть модуль 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!

Что я делаю неправильно?

Это было полезно?

Решение

Это может помочь подумать о том, как работа о пространствах имен 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_expende в пространстве имен локального модуля или в пространстве имен A_Package.baz, выше. Но из-за того, что бар импортирует do_something_expende напрямую, а не ссылаться на него от пространства имен модуля, вам нужно написать на свой пространство имен:

import bar
bar.do_something_expensive = lambda: 'Something really cheap.'

Другие советы

Там действительно элегантный декоратор для этого: Guido Van Rossum: Список Python-Dev: MonkeyPatching Idioms.

Есть также Dectools. Пакет, который я видел Pycon 2010, который может быть в состоянии использовать в этом контексте, но это может действительно пойти другим путем (обезьян на методе декларативный уровень ... где вы не)

Если вы хотите исправить его только для вашего звонка и оставить оригинальный код, который вы можете использовать https://docs.yphon.org/3/Library/unittest.mock.html#patch.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!'

В первом фрагменте вы делаете bar.do_something_expensive Обратитесь к объекту функции, который a_package.baz.do_something_expensive относится к этому моменту. Для действительно «обезьян» вам нужно будет изменить саму функцию (вы меняли только какие имена относятся); Это возможно, но вы на самом деле не хотите этого делать.

В ваших попытках изменить поведение a_function, Вы сделали две вещи:

  1. В первой попытке вы делаете Do_something_expenite глобальное имя в вашем модуле. Тем не менее, вы звоните a_function, который не смотрит в ваш модуль для разрешения имен, поэтому он все еще относится к той же функции.

  2. Во втором примере вы измените то, что a_package.baz.do_something_expensive относится к, но bar.do_something_expensive не волшебно связан с этим. Это имя до сих пор относится к объекту функционала, который он посмотрел, когда он был инициализирован.

Самый простой, но далеко от идеального подхода будет изменяться bar.py сказать

import a_package.baz

def a_function():
    print a_package.baz.do_something_expensive()

Правильное решение, вероятно, одно из двух вещей:

  • Переопределить a_function взять функцию в качестве аргумента и позвоните, а не пытаться проникнуть и изменить, какую функцию жестко закодировано, или
  • Хранить функцию, которая будет использоваться в экземпляре класса; Это то, как мы делаем музное состояние в Python.

Использование глобалов (это то, что изменяется материал модуля из других модулей) плохо Это приводит к неизвестнимому, запутанному, неуместному, несуществующему кодуру потоку которого трудно отслеживать.

do_something_expensive в a_function() Функция - это просто переменная в пространстве имен модуля, указывающего на объект функции. Когда вы переопределите модуль, вы делаете это в другом пространстве имен.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top