Рекурсивная версия 'reload'
Когда я разрабатываю код Python, я обычно тестирую его в специальном режиме в интерпретаторе. Я буду import some_module
, протестировать его, найти ошибку, исправить ошибку и сохранить, а затем использовать встроенную функцию reload
для reload(some_module)
и снова протестировать.
Однако предположим, что в some_module
у меня есть import some_other_module
, и при тестировании some_module
я обнаруживаю ошибку в some_other_module
и исправляю ее. Теперь вызов reload(some_module)
не будет рекурсивно повторно импортировать some_other_module
. Мне нужно либо вручную, либо reimport зависимость (делая что-то вроде reload(some_module.some_other_module)
или import some_other_module; reload(some_other_module)
), или, если я изменил целую кучу зависимостей и потерял информацию о том, что мне нужно перезагрузить, мне нужно перезапустить весь интерпретатор.
Что было бы более удобно, если бы была какая-то функция recursive_reload
, и я мог просто сделать recursive_reload(some_module)
и Python не только перезагрузить some_module
, но и рекурсивно перезагрузить каждый модуль, который some_module
импортирует (и каждый модуль, который импортирует каждый из этих модулей и т.д.), чтобы я мог быть уверен, что не использовал старую версию любого из других модулей, от которых зависит some_module
.
Я не думаю, что в Python есть что-то, что ведет себя как функция recursive_reload
, которую я описываю здесь, но есть ли простой способ взломать такую вещь вместе?
Ответы
Ответ 1
Я столкнулся с той же проблемой, и ты вдохновил меня на ее решение.
from types import ModuleType
try:
from importlib import reload # Python 3.4+
except ImportError:
# Needed for Python 3.0-3.3; harmless in Python 2.7 where imp.reload is just an
# alias for the builtin reload.
from imp import reload
def rreload(module):
"""Recursively reload modules."""
reload(module)
for attribute_name in dir(module):
attribute = getattr(module, attribute_name)
if type(attribute) is ModuleType:
rreload(attribute)
Или, если вы используете IPython, просто используйте dreload
или передайте --deep-reload
при запуске.
Ответ 2
Не проще ли будет на самом деле написать несколько тестовых примеров и запускать их каждый раз, когда вы закончите с изменением вашего модуля?
То, что вы делаете, это круто (вы, по сути, используете TDD (тестовую разработку), но вы делаете это неправильно.
Учтите, что при написании модульных тестов (с использованием модуля pytest unittest по умолчанию или, еще лучше, nose) вы получите тесты, которые могут быть повторно использованы, стабильны и поможет вам обнаружить несоответствия в вашем коде намного намного быстрее и лучше, чем при тестировании вашего модуля в интерактивной среде.
Ответ 3
Я столкнулся с той же проблемой, и я набрал ответы @Mattew и @osa.
from types import ModuleType
import os, sys
def rreload(module, paths=None, mdict=None):
"""Recursively reload modules."""
if paths is None:
paths = ['']
if mdict is None:
mdict = {}
if module not in mdict:
# modules reloaded from this module
mdict[module] = []
reload(module)
for attribute_name in dir(module):
attribute = getattr(module, attribute_name)
if type(attribute) is ModuleType:
if attribute not in mdict[module]:
if attribute.__name__ not in sys.builtin_module_names:
if os.path.dirname(attribute.__file__) in paths:
mdict[module].append(attribute)
rreload(attribute, paths, mdict)
reload(module)
#return mdict
Существуют три отличия:
- В общем случае перезагрузка (модуль) также должна быть вызвана в конце функции, как указывал @osa.
- С циклическими зависимостями импорта код, ранее опубликованный, будет работать навсегда, поэтому я добавил словарь списков, чтобы отслеживать набор модулей, загружаемых другими модулями. Хотя круговые зависимости не круты, Python позволяет их, поэтому эта функция перезагрузки также имеет дело с ними.
- Я добавил список путей (по умолчанию это ['']), из которых разрешена перезагрузка. Некоторые модули не любят перезагружаться обычным способом (как показано здесь).
Ответ 4
Технически, в каждом файле вы можете поместить команду перезагрузки, чтобы гарантировать, что он перезагружается каждый раз, когда он импортирует
a.py:
def testa():
print 'hi!'
b.py:
import a
reload(a)
def testb():
a.testa()
Теперь интерактивно:
import b
b.testb()
#hi!
#<modify a.py>
reload(b)
b.testb()
#hello again!
Ответ 5
Код прекрасно работал для модулей зависимостей, импортированных так же, как import another_module
, но потерпел неудачу, когда модуль импортировал функции с from another_module import some_func
.
Я расширил ответ @redsk, чтобы попытаться проявить смекалку в отношении этих функций. Я также добавил черный список, потому что, к сожалению, typing
и importlib
не появляются в sys.builtin_module_names
(возможно, есть и другие). Также я хотел предотвратить перезагрузку некоторых зависимостей, о которых я знал.
Я также отслеживаю перезагруженные имена модулей и возвращаю их.
Протестировано на Python 3.7.4 для Windows:
def rreload(module, paths=None, mdict=None, base_module=None, blacklist=None, reloaded_modules=None):
"""Recursively reload modules."""
if paths is None:
paths = [""]
if mdict is None:
mdict = {}
if module not in mdict:
# modules reloaded from this module
mdict[module] = []
if base_module is None:
base_module = module
if blacklist is None:
blacklist = ["importlib", "typing"]
if reloaded_modules is None:
reloaded_modules = []
reload(module)
reloaded_modules.append(module.__name__)
for attribute_name in dir(module):
attribute = getattr(module, attribute_name)
if type(attribute) is ModuleType and attribute.__name__ not in blacklist:
if attribute not in mdict[module]:
if attribute.__name__ not in sys.builtin_module_names:
if os.path.dirname(attribute.__file__) in paths:
mdict[module].append(attribute)
reloaded_modules = rreload(attribute, paths, mdict, base_module, blacklist, reloaded_modules)
elif callable(attribute) and attribute.__module__ not in blacklist:
if attribute.__module__ not in sys.builtin_module_names and f"_{attribute.__module__}" not in sys.builtin_module_names:
if sys.modules[attribute.__module__] != base_module:
if sys.modules[attribute.__module__] not in mdict:
mdict[sys.modules[attribute.__module__]] = [attribute]
reloaded_modules = rreload(sys.modules[attribute.__module__], paths, mdict, base_module, blacklist, reloaded_modules)
reload(module)
return reloaded_modules
Некоторые заметки:
- Я не знаю, почему некоторым встроенным_модулям-именам предшествует знак подчеркивания (например,
collections
указан как _collections
, поэтому я должен выполнить проверку двойной строки.
callable()
возвращает True
для классов, я думаю, это ожидалось, но это было одной из причин, по которым мне пришлось занести в черный список дополнительные модули.
По крайней мере, теперь я могу глубоко перезагрузить модуль во время выполнения, и из своих тестов я смог пройти несколько уровней глубоко с from foo import bar
и видеть результат при каждом вызове rreload()
(Прошу прощения за длинную и уродливую глубину, но версия в черном формате выглядит не так читаемо на SO)
Ответ 6
Я нашел ответ Редск очень полезным. Я предлагаю упрощенную (для пользователя, а не в виде кода) версию, в которой путь к модулю собирается автоматически и рекурсия работает для произвольного числа уровней. Все содержится в одной функции. Проверено на Python 3.4. Я предполагаю, что для Python 3.3 нужно import reload from imp
вместо ... from importlib
. Он также проверяет __file__
файла __file__
, который может быть ложным, если кодировщик забывает определить файл __init__.py
в подмодуле. В таком случае возникает исключение.
def rreload(module):
"""
Recursive reload of the specified module and (recursively) the used ones.
Mandatory! Every submodule must have an __init__.py file
Usage:
import mymodule
rreload(mymodule)
:param module: the module to load (the module itself, not a string)
:return: nothing
"""
import os.path
import sys
def rreload_deep_scan(module, rootpath, mdict=None):
from types import ModuleType
from importlib import reload
if mdict is None:
mdict = {}
if module not in mdict:
# modules reloaded from this module
mdict[module] = []
# print("RReloading " + str(module))
reload(module)
for attribute_name in dir(module):
attribute = getattr(module, attribute_name)
# print ("for attr "+attribute_name)
if type(attribute) is ModuleType:
# print ("typeok")
if attribute not in mdict[module]:
# print ("not int mdict")
if attribute.__name__ not in sys.builtin_module_names:
# print ("not a builtin")
# If the submodule is a python file, it will have a __file__ attribute
if not hasattr(attribute, '__file__'):
raise BaseException("Could not find attribute __file__ for module '"+str(attribute)+"'. Maybe a missing __init__.py file?")
attribute_path = os.path.dirname(attribute.__file__)
if attribute_path.startswith(rootpath):
# print ("in path")
mdict[module].append(attribute)
rreload_deep_scan(attribute, rootpath, mdict)
rreload_deep_scan(module, rootpath=os.path.dirname(module.__file__))
Ответ 7
Для Python 3. 6+ вы можете использовать:
from types import ModuleType
import sys
import importlib
def deep_reload(m: ModuleType):
name = m.__name__ # get the name that is used in sys.modules
name_ext = name + '.' # support finding sub modules or packages
def compare(loaded: str):
return (loaded == name) or loaded.startswith(name_ext)
all_mods = tuple(sys.modules) # prevent changing iterable while iterating over it
sub_mods = filter(compare, all_mods)
for pkg in sorted(sub_mods, key=lambda item: item.count('.'), reverse=True):
importlib.reload(sys.modules[pkg]) # reload packages, beginning with the most deeply nested
Ответ 8
Это сложная задача - у меня есть рабочий пример в этом ответе:
как найти список модулей, которые зависят от конкретного модуля в python