Рекурсивная версия 'reload' - программирование
Подтвердить что ты не робот

Рекурсивная версия '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, которую я описываю здесь, но есть ли простой способ взломать такую ​​вещь вместе?

4b9b3361

Ответ 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

Некоторые заметки:

  1. Я не знаю, почему некоторым встроенным_модулям-именам предшествует знак подчеркивания (например, collections указан как _collections, поэтому я должен выполнить проверку двойной строки.
  2. 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