Как выгрузить (перезагрузить) модуль Python?
У меня есть долго работающий сервер Python, и я хотел бы иметь возможность обновить службу без перезапуска сервера. Какой лучший способ сделать это?
if foo.py has changed:
unimport foo <-- How do I do this?
import foo
myfoo = foo.Foo()
23 ответа
Вы можете перезагрузить модуль, когда он уже был импортирован, используя reload
встроенная функция:
from importlib import reload # Python 3.4+ only.
import foo
while True:
# Do some things.
if is_changed(foo):
foo = reload(foo)
В Python 3 reload
был перемещен в imp
модуль. В 3.4 imp
устарела в пользу importlib
, а также reload
был добавлен к последнему. При таргетинге на 3 или более позднюю версию, при вызове используйте либо соответствующий модуль reload
или импортировать его.
Я думаю, что это то, что вы хотите. Веб-серверы, такие как сервер разработки Django, используют это для того, чтобы вы могли видеть последствия изменений вашего кода, не перезапуская сам процесс сервера.
Цитировать из документов:
Код модулей Python перекомпилируется, и код уровня модуля повторно выполняется, определяя новый набор объектов, которые связаны с именами в словаре модуля. Функция init модулей расширения не вызывается во второй раз. Как и со всеми другими объектами в Python, старые объекты восстанавливаются только после того, как их число ссылок упадет до нуля. Имена в пространстве имен модуля обновляются, чтобы указывать на любые новые или измененные объекты. Другие ссылки на старые объекты (такие как имена, внешние по отношению к модулю) не возвращаются для ссылки на новые объекты и должны обновляться в каждом пространстве имен, где они встречаются, если это необходимо.
Как вы отметили в своем вопросе, вам придется реконструировать Foo
объекты, если Foo
класс проживает в foo
модуль.
В Python 3.0–3.3 вы бы использовали: imp.reload(module)
BDFL ответил на этот вопрос.
Тем не мение, imp
устарел в 3.4, в пользу importlib
(спасибо Stefan).
Я думаю, поэтому, вы бы сейчас использовать importlib.reload(module)
хотя я не уверен.
Может быть особенно трудно удалить модуль, если он не является чистым Python.
Вот некоторая информация из: Как мне действительно удалить импортированный модуль?
Вы можете использовать sys.getrefcount(), чтобы узнать фактическое количество ссылок.
>>> import sys, empty, os
>>> sys.getrefcount(sys)
9
>>> sys.getrefcount(os)
6
>>> sys.getrefcount(empty)
3
Числа больше 3 указывают на то, что будет трудно избавиться от модуля. Доморощенный "пустой" (не содержащий ничего) модуль должен собираться после
>>> del sys.modules["empty"]
>>> del empty
третья ссылка - это артефакт функции getrefcount ().
reload(module)
, но только если он полностью автономен. Если что-то еще имеет ссылку на модуль (или любой объект, принадлежащий модулю), то вы получите тонкие и любопытные ошибки, вызванные старым кодом, который висит дольше, чем вы ожидали, и такими вещами, как isinstance
не работает в разных версиях одного и того же кода.
Если у вас есть односторонние зависимости, вы также должны перезагрузить все модули, которые зависят от перезагруженного модуля, чтобы избавиться от всех ссылок на старый код. А затем перезагрузите модули, которые зависят от загруженных модулей, рекурсивно.
Если у вас есть циклические зависимости, которые очень распространены, например, когда вы имеете дело с перезагрузкой пакета, вы должны выгрузить все модули в группе за один раз. Вы не можете сделать это с reload()
потому что он будет повторно импортировать каждый модуль до обновления его зависимостей, позволяя старым ссылкам проникать в новые модули.
Единственный способ сделать это в этом случае - взломать sys.modules
что-то вроде не поддерживается. Вы должны пройти и удалить каждый sys.modules
запись, которую вы хотите перезагрузить при следующем импорте, а также удалить записи, значения которых None
иметь дело с проблемой реализации, связанной с кэшированием неудачного относительного импорта. Это не очень приятно, но пока у вас есть полностью автономный набор зависимостей, который не оставляет ссылок вне своей кодовой базы, это работоспособно.
Вероятно, лучше перезагрузить сервер.:-)
Для Python 2 используйте встроенную функцию reload ():
reload(module)
Для Python 2 и 3.2–3.3 используйте перезагрузку из модуля imp:
import imp
imp.reload(module)
Но imp
устарела с версии 3.4 в пользу importlib, поэтому используйте:
import importlib
importlib.reload(module)
или же
from importlib import reload
reload(module)
Следующий код обеспечивает совместимость с Python 2/3:
try:
reload
except NameError:
# Python 3
from imp import reload
Вы можете использовать его как reload()
в обеих версиях, что делает вещи проще.
Если вы не на сервере, но разрабатываете и вам нужно часто перезагружать модуль, вот хороший совет.
Во-первых, убедитесь, что вы используете превосходную оболочку IPython из проекта Jupyter Notebook. После установки Jupyter вы можете запустить его с ipython
, или же jupyter console
или даже лучше, jupyter qtconsole
, которая даст вам красивую раскрашенную консоль с дополнением кода в любой ОС.
Теперь в вашей оболочке введите:
%load_ext autoreload
%autoreload 2
Теперь каждый раз, когда вы запускаете ваш скрипт, ваши модули будут перезагружаться.
За пределами 2
Есть и другие варианты магии автозагрузки:
%autoreload
Reload all modules (except those excluded by %aimport) automatically now.
%autoreload 0
Disable automatic reloading.
%autoreload 1
Reload all modules imported with %aimport every time before executing the Python code typed.
%autoreload 2
Reload all modules (except those excluded by %aimport) every time before
executing the Python code typed.
Принятый ответ не обрабатывает случай импорта X из Y. Этот код обрабатывает его, а также стандартный случай импорта:
def importOrReload(module_name, *names):
import sys
if module_name in sys.modules:
reload(sys.modules[module_name])
else:
__import__(module_name, fromlist=names)
for name in names:
globals()[name] = getattr(sys.modules[module_name], name)
# use instead of: from dfly_parser import parseMessages
importOrReload("dfly_parser", "parseMessages")
В случае перезагрузки мы переназначаем имена верхнего уровня значениям, хранящимся во вновь загруженном модуле, который обновляет их.
Это современный способ перезагрузки модуля:
from importlib import reload
Просто введите reload(MODULE_NAME)
, заменяя MODULE_NAME
с названием модуля, который вы хотите перезагрузить.
Например, reload(math)
перезагрузит математическую функцию.
Для таких как я, которые хотят выгрузить все модули (при работе в интерпретаторе Python под Emacs):
for mod in sys.modules.values():
reload(mod)
Более подробная информация находится в Перезагрузке модулей Python.
Изменить (ответ V2)
Предыдущее решение подходит только для получения информации о сбросе, но оно не изменит все ссылки (более reload
но меньше, чем требуется). Чтобы установить все ссылки, мне пришлось зайти в сборщик мусора и переписать там ссылки. Теперь это работает как шарм!
Обратите внимание, что это не сработает, если ГХ выключен или при перезагрузке данных, которые не контролируются ГХ. Если вы не хотите связываться с GC, вам может быть достаточно исходного ответа.
Новый код:
import importlib
import inspect
import gc
from weakref import ref
def reset_module(module, inner_modules_also=True):
"""
This function is a stronger form of importlib's `reload` function. What it does, is that aside from reloading a
module, it goes to the old instance of the module, and sets all the (not read-only) attributes, functions and classes
to be the reloaded-module's
:param module: The module to reload (module reference, not the name)
:param inner_modules_also: Whether to treat ths module as a package as well, and reload all the modules within it.
"""
# For the case when the module is actually a package
if inner_modules_also:
submods = {submod for _, submod in inspect.getmembers(module)
if (type(submod).__name__ == 'module') and (submod.__package__.startswith(module.__name__))}
for submod in submods:
reset_module(submod, True)
# First, log all the references before reloading (because some references may be changed by the reload operation).
module_tree = _get_tree_references_to_reset_recursively(module, module.__name__)
new_module = importlib.reload(module)
_reset_item_recursively(module, module_tree, new_module)
def _update_referrers(item, new_item):
refs = gc.get_referrers(item)
weak_ref_item = ref(item)
for coll in refs:
if type(coll) == dict:
enumerator = coll.keys()
elif type(coll) == list:
enumerator = range(len(coll))
else:
continue
for key in enumerator:
if weak_ref_item() is None:
# No refs are left in the GC
return
if coll[key] is weak_ref_item():
coll[key] = new_item
def _get_tree_references_to_reset_recursively(item, module_name, grayed_out_item_ids = None):
if grayed_out_item_ids is None:
grayed_out_item_ids = set()
item_tree = dict()
attr_names = set(dir(item)) - _readonly_attrs
for sub_item_name in attr_names:
sub_item = getattr(item, sub_item_name)
item_tree[sub_item_name] = [sub_item, None]
try:
# Will work for classes and functions defined in that module.
mod_name = sub_item.__module__
except AttributeError:
mod_name = None
# If this item was defined within this module, deep-reset
if (mod_name is None) or (mod_name != module_name) or (id(sub_item) in grayed_out_item_ids) \
or isinstance(sub_item, EnumMeta):
continue
grayed_out_item_ids.add(id(sub_item))
item_tree[sub_item_name][1] = \
_get_tree_references_to_reset_recursively(sub_item, module_name, grayed_out_item_ids)
return item_tree
def _reset_item_recursively(item, item_subtree, new_item):
# Set children first so we don't lose the current references.
if item_subtree is not None:
for sub_item_name, (sub_item, sub_item_tree) in item_subtree.items():
try:
new_sub_item = getattr(new_item, sub_item_name)
except AttributeError:
# The item doesn't exist in the reloaded module. Ignore.
continue
try:
# Set the item
_reset_item_recursively(sub_item, sub_item_tree, new_sub_item)
except Exception as ex:
pass
_update_referrers(item, new_item)
Оригинальный ответ
Как написано в ответе @bobince, если уже есть ссылка на этот модуль в другом модуле (особенно если он был импортирован с as
ключевое слово вроде import numpy as np
), этот экземпляр не будет перезаписан.
Это оказалось для меня довольно проблематичным при применении тестов, которые требовали "чистого" состояния модулей конфигурации, поэтому я написал функцию с именем reset_module
который использует importlib
с reload
функция и рекурсивно перезаписывает все объявленные атрибуты модуля. Он был протестирован с Python версии 3.6.
import importlib
import inspect
from enum import EnumMeta
_readonly_attrs = {'__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__',
'__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__func__', '__ge__', '__get__',
'__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__',
'__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__',
'__reduce__', '__reduce_ex__', '__repr__', '__self__', '__setattr__', '__sizeof__', '__str__',
'__subclasshook__', '__weakref__', '__members__', '__mro__', '__itemsize__', '__isabstractmethod__',
'__basicsize__', '__base__'}
def reset_module(module, inner_modules_also=True):
"""
This function is a stronger form of importlib's `reload` function. What it does, is that aside from reloading a
module, it goes to the old instance of the module, and sets all the (not read-only) attributes, functions and classes
to be the reloaded-module's
:param module: The module to reload (module reference, not the name)
:param inner_modules_also: Whether to treat ths module as a package as well, and reload all the modules within it.
"""
new_module = importlib.reload(module)
reset_items = set()
# For the case when the module is actually a package
if inner_modules_also:
submods = {submod for _, submod in inspect.getmembers(module)
if (type(submod).__name__ == 'module') and (submod.__package__.startswith(module.__name__))}
for submod in submods:
reset_module(submod, True)
_reset_item_recursively(module, new_module, module.__name__, reset_items)
def _reset_item_recursively(item, new_item, module_name, reset_items=None):
if reset_items is None:
reset_items = set()
attr_names = set(dir(item)) - _readonly_attrs
for sitem_name in attr_names:
sitem = getattr(item, sitem_name)
new_sitem = getattr(new_item, sitem_name)
try:
# Set the item
setattr(item, sitem_name, new_sitem)
try:
# Will work for classes and functions defined in that module.
mod_name = sitem.__module__
except AttributeError:
mod_name = None
# If this item was defined within this module, deep-reset
if (mod_name is None) or (mod_name != module_name) or (id(sitem) in reset_items) \
or isinstance(sitem, EnumMeta): # Deal with enums
continue
reset_items.add(id(sitem))
_reset_item_recursively(sitem, new_sitem, module_name, reset_items)
except Exception as ex:
raise Exception(sitem_name) from ex
Примечание: используйте осторожно! Их использование в непериферийных модулях (например, в модулях, определяющих классы, используемые извне) может привести к внутренним проблемам в Python (например, проблемам травления / расправления).
Те, кто использует Python 3 и перезагрузить из importlib.
Если у вас возникли проблемы, например, кажется, что модуль не перезагружается... Это потому, что ему нужно некоторое время для перекомпиляции pyc (до 60 секунд). Я пишу этот совет только для того, чтобы вы знали, что у вас возникла такая проблема.
Enthought Traits имеет модуль, который довольно хорошо работает для этого. https://traits.readthedocs.org/en/4.3.0/_modules/traits/util/refresh.html
Он перезагрузит любой модуль, который был изменен, и обновит другие модули и экземпляры, которые его используют. Это не работает большую часть времени с __very_private__
методы, и может подавить наследование классов, но это избавляет меня от сумасшедших затрат времени от необходимости перезапускать хост-приложение при написании PyQt-приложений или вещи, которые запускаются внутри таких программ, как Maya или Nuke. Это не работает, может быть, 20-30 % времени, но все равно невероятно полезно.
Пакет Enthought не перезагружает файлы в тот момент, когда они меняются - вы должны называть это явно - но это не должно быть так сложно реализовать, если вам это действительно нужно
Другой вариант. Посмотрите, что Python по умолчанию importlib.reload
просто повторно импортирует библиотеку, переданную в качестве аргумента. Он не перезагрузит библиотеки, которые импортирует ваша библиотека. Если вы изменили много файлов и у вас есть несколько сложный пакет для импорта, вы должны выполнить глубокую перезагрузку.
Если у вас установлен IPython или Jupyter, вы можете использовать функцию для глубокой перезагрузки всех библиотек:
from IPython.lib.deepreload import reload as dreload
dreload(foo)
Если у вас нет Jupyter, установите его с помощью этой команды в вашей оболочке:
pip3 install jupyter
2018-02-01
- модуль
foo
должны быть успешно импортированы заранее. from importlib import reload
,reload(foo)
31.5. importlib - Реализация импорта - документация Python 3.6.4
Если вы столкнулись со следующей ошибкой, этот ответ может помочь вам найти решение:
Отслеживание (последний вызов последний): Файл "FFFF", строка 1, в NameError: имя "ГГГГ" не определено
ИЛИ
Отслеживание (последний вызов последний): Файл "FFFF", строка 1, в Файл "/usr/local/lib/python3.7/importlib/__init__.py", строка 140, при перезагрузке поднять TypeError("аргумент reload() должен быть модулем") TypeError: аргумент reload() должен быть модулем
Если у вас есть импорт, подобный приведенному ниже, вам может потребоваться использовать sys.modules
чтобы получить модуль, который вы хотите перезагрузить:
from YYYY.XXX.ZZZ import CCCC
import AAA.BBB.CC
def reload(full_name)
import importlib
import sys
if full_name in sys.modules:
importlib.reload(sys.modules[full_name])
reload('YYYY.XXX.ZZZ') # this is fine in both cases
reload('AAA.BBB.CC')
importlib.reload(YYYY.XXX.ZZZ) # in my case: this fails
importlib.reload(AAA.BBB.CC) # and this is ok
Основная проблема в том, что importlib.reload
принимает только модуль, а не строку.
Для меня в случае Abaqus это так, как это работает. Представьте, что ваш файл Class_VerticesEdges.py
sys.path.append('D:\...\My Pythons')
if 'Class_VerticesEdges' in sys.modules:
del sys.modules['Class_VerticesEdges']
print 'old module Class_VerticesEdges deleted'
from Class_VerticesEdges import *
reload(sys.modules['Class_VerticesEdges'])
Python не будет пересчитывать адрес подмодуля, когда
reload
, событие, если оно находится в
sys.modules
Вот обходной путь, не идеальный, но работающий.
# Created by BaiJiFeiLong@gmail.com at 2022/2/19 18:50
import importlib
import types
import urllib.parse
import urllib.request
def reloadModuleWithChildren(mod):
mod = importlib.reload(mod)
for k, v in mod.__dict__.items():
if isinstance(v, types.ModuleType):
setattr(mod, k, importlib.import_module(v.__name__))
fakeParse = types.ModuleType("urllib.parse")
realParse = urllib.parse
urllib.parse = fakeParse
assert urllib.parse is fakeParse
importlib.reload(urllib)
assert urllib.parse is fakeParse
assert getattr(urllib, "parse") is fakeParse
reloadModuleWithChildren(urllib)
assert urllib.parse is not fakeParse
assert urllib.parse is realParse
Для удаления модулей из sys.modules необходимо также удалить типы "None".
Способ 1:
import sys
import json ## your module
for mod in [ m for m in sys.modules if m.lstrip('_').startswith('json') or sys.modules[m] == None ]: del sys.modules[mod]
print( json.dumps( [1] ) ) ## test if functionality has been removed
Метод 2 с использованием бухгалтерских записей для удаления всех зависимостей:
import sys
before_import = [mod for mod in sys.modules]
import json ## your module
after_import = [mod for mod in sys.modules if mod not in before_import]
for mod in [m for m in sys.modules if m in after_import or sys.modules[m] == None]: del sys.modules[mod]
print( json.dumps( [2] ) ) ## test if functionality has been removed
Необязательно, чтобы убедиться, что все записи отсутствуют, если вы решите:
import gc
gc.collect()
У меня было много проблем при попытке перезагрузить что-то внутри Sublime Text, но, наконец, я мог написать эту утилиту для перезагрузки модулей на Sublime Text на основе кода sublime_plugin.py
использует для перезагрузки модулей.
Это ниже позволяет вам перезагрузить модули из путей с пробелами в их именах, а затем после перезагрузки вы можете просто импортировать как обычно.
def reload_module(full_module_name):
"""
Assuming the folder `full_module_name` is a folder inside some
folder on the python sys.path, for example, sys.path as `C:/`, and
you are inside the folder `C:/Path With Spaces` on the file
`C:/Path With Spaces/main.py` and want to re-import some files on
the folder `C:/Path With Spaces/tests`
@param full_module_name the relative full path to the module file
you want to reload from a folder on the
python `sys.path`
"""
import imp
import sys
import importlib
if full_module_name in sys.modules:
module_object = sys.modules[full_module_name]
module_object = imp.reload( module_object )
else:
importlib.import_module( full_module_name )
def run_tests():
print( "\n\n" )
reload_module( "Path With Spaces.tests.semantic_linefeed_unit_tests" )
reload_module( "Path With Spaces.tests.semantic_linefeed_manual_tests" )
from .tests import semantic_linefeed_unit_tests
from .tests import semantic_linefeed_manual_tests
semantic_linefeed_unit_tests.run_unit_tests()
semantic_linefeed_manual_tests.run_manual_tests()
if __name__ == "__main__":
run_tests()
Если вы запускаете в первый раз, это должно загрузить модуль, но если позже вы можете снова метод / функцию run_tests()
это перезагрузит тестовые файлы. С возвышенным текстом (Python 3.3.6
) это часто случается, потому что его интерпретатор никогда не закрывается (если вы не перезапустите Sublime Text, т.е. Python3.3
переводчик).
unload
Операция может быть легко выполнена следующим образом:
import pythonlib
del pythonlib
у вас больше не будет ничего общего с этой библиотекой.
Другим способом может быть импорт модуля в функцию. Таким образом, когда функция завершает работу, модуль получает мусор.