Запретить Python кэшировать импортированные модули - PullRequest
44 голосов
/ 27 мая 2010

При разработке крупного проекта (разделенного на несколько файлов и папок) в 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 так сильно отличается мой модуль в одном большом файле или в нескольких подмодулях? Почему этот подход не работает ??

Ответы [ 8 ]

24 голосов
/ 27 мая 2010

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
10 голосов
/ 09 июля 2013

С 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
10 голосов
/ 27 мая 2010

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

4 голосов
/ 23 апреля 2014

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

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

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

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

НТН

2 голосов
/ 18 ноября 2016

Я использую 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();
2 голосов
/ 12 октября 2010

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

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

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

0 голосов
/ 26 марта 2019

Для Python версии 3.4 и выше

import importlib 
importlib.reload(<package_name>) 
from <package_name> import <method_name>

Подробнее см. Ниже документация .

0 голосов
/ 01 февраля 2019

Подумайте дважды, чтобы выйти и возобновить производство

Простое решение без выхода и перезапуска - использование перезагрузки из imp

import moduleA, moduleB
from imp import reload
reload (moduleB)
...