Запретить Python кэшировать импортированные модули

при разработке большого проекта (разделенного на несколько файлов и папок) в Python с IPython я столкнулся с проблемой кэширования импортированных модулей.

проблема в том, что инструкции import module только считывает модуль один раз, даже если этот модуль изменился! Поэтому каждый раз, когда я что-то меняю в своем пакете, я должен выйти и перезапустить IPython. Болезненный.

есть ли способ правильно перезагрузить некоторые модули? Или, лучше, каким-то образом предотвратить кэширование Python их?

Я пробовал несколько подходов, но ни один не работает. В частности, я сталкиваюсь с очень, очень странными ошибками, такими как некоторые модули или переменные таинственно становятся равными None...

единственный разумный ресурс, который я нашел, это перезагрузка модулей Python, от pyunit, но я не проверил его. Мне бы хотелось чего-то подобного.

хорошей альтернативой было бы перезапустить IPython или перезапустить интерпретатор Python.

так, если вы разрабатываете Python, какое решение вы нашли для этой проблемы?

редактировать

чтобы было понятно: очевидно, я понимаю, что некоторые старые переменные в зависимости от предыдущего состояния модуля может остаться. Меня это устраивает. Почему в 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, есть ли у меня модуль в одном большом файле или в нескольких подмодулях? Почему такой подход не сработает??

6 ответов


выход и перезапуск интерпретатора-лучшее решение. Любая стратегия живой перезагрузки или без кэширования не будет работать плавно, потому что объекты из больше не существующих модулей могут существовать и потому, что модули иногда хранят состояние и потому, что даже если ваш вариант использования действительно позволяет горячую перезагрузку, слишком сложно думать о том, чтобы это стоило.


import проверяет, находится ли модуль в sys.modules, и если это так,он возвращает его. Если вы хотите импортировать для загрузки модуля с диска, вы можете удалить соответствующий ключ в sys.modules первый.

есть reload встроенная функция, которая, учитывая объект модуля, перезагрузит его с диска, и это будет помещено в sys.modules. редактировать -- на самом деле, он будет перекомпилировать код из файла на диске, а затем повторно evalute его в существующий модуль __dict__. Что-то потенциально очень отличное от создания нового объекта модуля.

Майк Грэм прав, хотя; получение перезагрузки правильно, если у вас есть даже несколько живых объектов, которые ссылаются на содержимое модуля, который вы больше не хотите, трудно. Существующие объекты по-прежнему будут ссылаться на классы, из которых они были созданы, - это очевидная проблема, но также и все ссылки, созданные с помощью from module import symbol будет указывать на любой объект из старой версии модуля. Много тонко возможны неправильные вещи.

Edit: я согласен с консенсусом, что перезапуск интерпретатора-это, безусловно, самая надежная вещь. Но для целей отладки, я думаю, вы можете попробовать что-то вроде следующего. Я уверен, что есть угловые случаи, для которых это не сработает, но если вы не делаете ничего слишком сумасшедшего (иначе) с загрузкой модуля в код пакет, это может быть полезно.

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

С 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

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

С Оболочкой IPython.движение за освобождение.модуль deepreload позволяет рекурсивно перезагружать модуль: изменения, внесенные в любую из его зависимостей, будут перезагружены без необходимости выходить. Чтобы начать использовать его, do:

http://ipython.org/ipython-doc/dev/interactive/reference.html#dreload

Он доступен как "глобальный" в ноутбуке IPython (по крайней мере, моя версия, в которой работает v2.0).

HTH


вы можете использовать импорт крюк машины, описанные в PEP 302 чтобы загрузить не сами модули, а какой - то прокси-объект, который позволит вам делать все, что вы хотите с базовым объектом модуля-перезагрузить его, удалить ссылку на него и т. д.

дополнительным преимуществом является то, что ваш существующий код не требует изменений, и эта дополнительная функциональность модуля может быть оторвана от одной точки в коде-где вы фактически добавляете finder в sys.meta_path.

некоторые мысли о реализации: создайте finder, который согласится найти любой модуль, кроме builtin (вы не имеете ничего общего со встроенными модулями), затем создайте загрузчик, который вернет объект прокси-класса из types.ModuleType вместо реального объекта модуля. Обратите внимание, что объект loader не вынужден создавать явные ссылки на загруженные модули в sys.modules, но это сильно поощряется, потому что, как вы уже видели, он может неожиданно потерпеть неудачу. Прокси-объект должен поймать и вперед все __getattr__, __setattr__ и __delattr__ к основному реальному модулю он держит ссылку. Вам, вероятно, не нужно будет определять __getattribute__ из-за вас не будет скрывать реальное содержимое модуля с вашими прокси-методами. Итак, теперь вы должны общаться с прокси-в некотором роде - вы можете создать некоторые специальные средства для снижения базовых, то модуль импорт, выписка справки из возвращенным прокси, падение прокси и удерживайте ссылку на модуль перезагружается. Фу, выглядит страшно, но должен исправить проблема без перезагрузки Python каждый раз.


Я использую 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();