Предотвращение кэширования Python импортированными модулями
При разработке большого проекта (разбитого на несколько файлов и папок) на Python с помощью IPython я столкнулся с проблемой кэшированных импортированных модулей.
Проблема в том, что команды import module
читают модуль только один раз, даже если этот модуль изменился! Поэтому каждый раз, когда я что-то меняю в своем пакете, я должен выйти и перезапустить IPython. Болезненные.
Есть ли способ принудительно перезагрузить некоторые модули? Или, лучше, чтобы каким-то образом запретить Python кэшировать их?
Я попробовал несколько подходов, но никто не работает. В частности, я сталкиваюсь с действительно, действительно странными ошибками, например, некоторыми модулями или переменными, таинственно становясь равными None
...
Единственный разумный ресурс, который я нашел, - Перезагрузка модулей Python, из pyunit, но я его не проверял. Я хотел бы что-то подобное.
Хорошей альтернативой было бы перезапуск IPython или каким-то образом перезапустить интерпретатор Python.
Итак, если вы разрабатываете в Python, какое решение вы нашли для этой проблемы?
Edit
Чтобы все было ясно: очевидно, я понимаю, что некоторые старые переменные, зависящие от предыдущего состояния модуля, могут остаться. Это прекрасно. Почему в Python так сложно заставить перезагрузить модуль, не имея каких-либо странных ошибок?
В частности, если у меня есть весь мой модуль в одном файле module.py
, то следующее прекрасно работает:
import sys
try:
del sys.modules['module']
except AttributeError:
pass
import module
obj = module.my_class()
Эта часть кода работает красиво, и я могу развиваться, не выходя из IPython в течение нескольких месяцев.
Однако, когда мой модуль состоит из нескольких подмодулей, ад разрывается:
import os
for mod in ['module.submod1', 'module.submod2']:
try:
del sys.module[mod]
except AttributeError:
pass
# sometimes this works, sometimes not. WHY?
Почему это так отличается для Python, есть ли у меня мой модуль в одном большом файле или в нескольких подмодулях? Почему этот подход не работает?
Ответы
Ответ 1
Выйти и перезапустить интерпретатор - лучшее решение. Любая стратегия живой перезагрузки или без кэширования не будет работать бесперебойно, потому что объекты из более не существующих модулей могут существовать и потому, что модули иногда хранят состояние, и потому, что даже если ваш прецедент действительно позволяет горячую перезагрузку, это слишком сложно, чтобы думать о стоит того.
Ответ 2
import
проверяет, находится ли модуль в sys.modules
, и если он есть, он возвращает его. Если вы хотите, чтобы импорт загружал модуль с диска, вы можете сначала удалить соответствующий ключ в sys.modules
.
Существует встроенная функция reload
, которая, учитывая объект модуля, перезагрузит ее с диска и будет размещена в sys.modules
. Изменить - на самом деле, он перекомпилирует код из файла на диске, а затем повторно проверит его в существующем модуле __dict__
. Что-то потенциально очень отличается от создания нового объекта модуля.
Майк Грэхем прав; получение перезагрузки прямо, если у вас есть еще несколько живых объектов, которые ссылаются на содержимое модуля, который вам больше не нужен, сложно. Существующие объекты по-прежнему будут ссылаться на классы, из которых они были созданы, является очевидной проблемой, но и все ссылки, созданные с помощью from module import symbol
, будут по-прежнему указывать на любой объект из старой версии модуля. Возможно много тонких неправильных вещей.
Изменить: Я согласен с консенсусом в отношении того, что перезапуск переводчика является, безусловно, самой надежной вещью. Но для целей отладки, я думаю, вы могли бы попробовать что-то вроде следующего. Я уверен, что есть угловые случаи, для которых это не сработает, но если вы не делаете ничего сумасшедшего (иначе) с загрузкой модуля в свой пакет, это может быть полезно.
def reload_package(root_module):
package_name = root_module.__name__
# get a reference to each loaded module
loaded_package_modules = dict([
(key, value) for key, value in sys.modules.items()
if key.startswith(package_name) and isinstance(value, types.ModuleType)])
# delete references to these loaded modules from sys.modules
for key in loaded_package_modules:
del sys.modules[key]
# load each of the modules again;
# make old modules share state with new modules
for key in loaded_package_modules:
print 'loading %s' % key
newmodule = __import__(key)
oldmodule = loaded_package_modules[key]
oldmodule.__dict__.clear()
oldmodule.__dict__.update(newmodule.__dict__)
Что я очень коротко проверил так:
import email, email.mime, email.mime.application
reload_package(email)
Печать
reloading email.iterators
reloading email.mime
reloading email.quoprimime
reloading email.encoders
reloading email.errors
reloading email
reloading email.charset
reloading email.mime.application
reloading email._parseaddr
reloading email.utils
reloading email.mime.base
reloading email.message
reloading email.mime.nonmultipart
reloading email.base64mime
Ответ 3
С IPython появляется расширение автозагрузки, которое автоматически повторяет импорт перед каждым вызовом функции. Он работает, по крайней мере, в простых случаях, но не слишком полагается на него: по моему опыту время от времени по-прежнему требуется перезапуск интерпретатора, особенно когда изменения кода происходят только на косвенно импортированном коде.
Пример использования со связанной страницы:
In [1]: %load_ext autoreload
In [2]: %autoreload 2
In [3]: from foo import some_function
In [4]: some_function()
Out[4]: 42
In [5]: # open foo.py in an editor and change some_function to return 43
In [6]: some_function()
Out[6]: 43
Ответ 4
Здесь есть несколько действительно хороших ответов, но стоит знать о dreload, которая является функцией, доступной в IPython, которая выполняет "глубокую перезагрузку". Из документации:
Модуль IPython.lib.deepreload позволяет рекурсивно перезагружать модуль: изменения, внесенные в любую из его зависимостей, будут перезагружены без необходимости выхода. Чтобы начать использовать его, выполните следующие действия:
http://ipython.org/ipython-doc/dev/interactive/reference.html#dreload
Он доступен как "глобальный" в ноутбуке IPython (по крайней мере, моя версия, на которой работает версия 2.0).
НТН
Ответ 5
Вы можете использовать механизмы захвата ввоза, описанные в PEP 302, чтобы загружать не сами модули, а какой-то прокси-объект, который позволит вам что-либо сделать вы хотите с базовым объектом модуля - перезагрузить его, отбросить ссылку на него и т.д.
Дополнительным преимуществом является то, что существующий в настоящее время код не требует изменений, и эта дополнительная функциональность модуля может быть оторвана от одной точки в коде - там, где вы действительно добавляете finder в sys.meta_path
.
Некоторые соображения по внедрению: создайте finder, который согласится найти любой модуль, кроме встроенного (вы не имеете ничего общего со встроенными модулями), затем создайте загрузчик, который будет возвращать объект-прокси подкласса из types.ModuleType
вместо реального объекта модуля, Обратите внимание, что объект-загрузчик не вынужден создавать явные ссылки на загруженные модули в sys.modules
, но он настоятельно рекомендуется, потому что, как вы уже видели, он может неожиданно завершиться. Объект прокси должен перехватывать и пересылать все __getattr__
, __setattr__
и __delattr__
в базовый реальный модуль, на котором он ссылается. Вам, вероятно, не нужно будет определять __getattribute__
из-за того, что вы не скрывали бы содержимое реального модуля с помощью ваших прокси-методов. Итак, теперь вы должны каким-то образом взаимодействовать с прокси-сервером - вы можете создать специальный метод для удаления базовой ссылки, затем импортировать модуль, извлечь ссылку из возвращенного прокси-сервера, сбросить прокси-сервер и сохранить ссылку на перезагруженный модуль. Фу, выглядит страшно, но нужно исправить вашу проблему, не перезагружая Python каждый раз.
Ответ 6
Я использую PythonNet в своем проекте. К счастью, я обнаружил, что есть команда, которая может прекрасно решить эту проблему.
using (Py.GIL())
{
dynamic mod = Py.Import(this.moduleName);
if (mod == null)
throw new Exception( string.Format("Cannot find module {0}. Python script may not be complied successfully or module name is illegal.", this.moduleName));
// This command works perfect for me!
PythonEngine.ReloadModule(mod);
dynamic instance = mod.ClassName();
Ответ 7
Подумайте дважды, чтобы выйти и перезапустить производство
Простое решение без выхода и перезапуска с помощью перезагрузки из Imp
import moduleA, moduleB
from imp import reload
reload (moduleB)