39

When I'm developing Python code, I usually test it in an ad-hoc way in the interpreter. I'll import some_module, test it, find a bug, fix the bug and save, and then use the built-in reload function to reload(some_module) and test again.

However, suppose that in some_module I have import some_other_module, and while testing some_module I discover a bug in some_other_module and fix it. Now calling reload(some_module) won't recursively re-import some_other_module. I have to either manually reimport the dependency (by doing something like reload(some_module.some_other_module), or import some_other_module; reload(some_other_module), or, if I've changed a whole bunch of dependencies and lost track of what I need to reload, I need to restart the entire interpreter.

What'd be more convenient is if there were some recursive_reload function, and I could just do recursive_reload(some_module) and have Python not only reload some_module, but also recursively reload every module that some_module imports (and every module that each of those modules imports, and so on) so that I could be sure that I wasn't using an old version of any of the other modules upon which some_module depends.

I don't think there's anything built in to Python that behaves like the recursive_reload function I describe here, but is there an easy way to hack such a thing together?

Mark Amery
  • 143,130
  • 81
  • 406
  • 459
  • possible duplicate of [how to find list of modules which depend upon a specific module in python](http://stackoverflow.com/questions/1827629/how-to-find-list-of-modules-which-depend-upon-a-specific-module-in-python) – jsbueno Mar 19 '13 at 18:00

11 Answers11

40

I've run up against the same issue, and you inspired me to actually solve the problem.

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)

Or, if you are using IPython, just use dreload or pass --deep-reload on startup.

Mark Amery
  • 143,130
  • 81
  • 406
  • 459
Matthew
  • 416
  • 5
  • 3
  • 1
    Actually, now that I am experimenting with it, we may want to call `reload(module)` twice; before and after reloading submodules. We need to call it before in case it refers any new modules. We need to call it after to handle statements such as `from X import Y`. If `X` has just been reloaded, we still need to reimport `Y`. I suspect it can be much more tricky, so we'd need to keep reloading until the dust settles. – Sergey Orshanskiy Jan 02 '15 at 18:58
  • 1
    The above implementation of rreload can lead to infinite recursion (in my case, within the `threading` module). Consider a graph DFS modification: https://gist.github.com/shwang/09bd0ffef8ef65af817cf977ce4698b8 – twink_ml Aug 06 '19 at 00:38
  • importlib.reload would work after this merge. https://github.com/streamlit/streamlit/pull/537/files/88d316b8a8eab24fd78a9d32564d799875b39bfe#diff-081b31a3d0f9c7948c3f7c7c973b3f09 – SetupX Nov 06 '19 at 07:43
  • 1
    you should put reload(module) after rreload all sub-modules – kkkobelief24 Mar 04 '20 at 06:12
  • sometimes I'll just keep a "half-script" that runs what I want until I get to a certain point, then run it with `python -i whatever`. This, plus `breakpoint()` can usually get me what I want... – Nathan Chappell Dec 23 '20 at 15:39
6

I've run against the same issue and I've built up on @Mattew and @osa answer.

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

There are three differences:

  1. In the general case, reload(module) has to be called at the end of the function as well, as @osa pointed out.
  2. With circular import dependencies the code posted earlier would loop forever so I've added a dictionary of lists to keep track of the set of modules loaded by other modules. While circular dependencies are not cool, Python allows them, so this reload function deals with them as well.
  3. I've added a list of paths (default is ['']) from which the reloading is allowed. Some modules don't like been reloaded the normal way, (as shown here).
Pandawan
  • 2,027
  • 1
  • 18
  • 24
redsk
  • 261
  • 6
  • 11
  • Doesn't work for me. It doesn't end in the infinite loop like the other answer from Matthew but no reload in the submodules actually happens – Jan Pisl Mar 17 '23 at 09:47
4

The code worked great for dependency modules imported just as import another_module, but it failed when the module imported functions with from another_module import some_func.

I expanded on @redsk's answer to try and be smart about these functions. I've also added a blacklist because unfortunately typing and importlib don't appear in sys.builtin_module_names (maybe there are more). Also I wanted to prevent reloading of some dependencies I knew about.

I also track the reloaded module names and return them.

Tested on 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

Some notes:

  1. I don't know why some builtin_module_names are prefixed with an underscore (for example collections is listed as _collections, so I have to do the double string check.
  2. callable() returns True for classes, I guess that's expected but that was one of the reasons I had to blacklist extra modules.

At least now I am able to deep reload a module at runtime and from my tests I was able to go multiple levels deep with from foo import bar and see the result at each call to rreload()

(Apologies for the long and ugly depth, but the black formatted version doesn't look so readable on SO)

f0ff886f
  • 41
  • 4
  • I had two problems with this, I had to create a check to see if attribute had the __module__ attribut, and also another check to see if attribute.__module__ was in sys.modules. To recreate this, install websocket-client and run rreload on it – Miojo_Esperto Aug 30 '20 at 00:29
3

Wouldn't it be simpler to actually write some test cases and run them every time you are done with modifying your module?

What you are doing is cool (you are in essence using TDD (test driven development) but you are doing it wrong.

Consider that with written unit tests(using the default python unittest module, or better yet nose) you get to have tests that are reusable, stable and help you detect inconsitencies in your code much much faster and better than with testing your module in the interactive environment.

NlightNFotis
  • 9,559
  • 5
  • 43
  • 66
  • +1, It sounds like this testing methodology has been outgrown. It doesn't need to even be real cases, just have a small script that keep as a scratch pad for testing code (if the project is small enough that full testing is overkill). – Gareth Latty Mar 19 '13 at 17:59
  • 5
    Irony: I was provoked to ask this question after I hit upon this issue *while testing some unit tests I was writing for another application*. If I were to apply your advice here, I'd end up infinitely recursing and creating endless layers of tests. ;) – Mark Amery Mar 19 '13 at 18:02
  • 4
    More seriously: proper tests of course have their place, but sometimes a more casual approach is called for (even if it's in addition to, rather than instead of, proper tests) and part of what's nice about Python is that it makes that casual approach easy. Maybe I've written half a function and want to check that so far it outputs what I expect, or maybe I've just chucked in a whole bunch of print statements into some tricky code and want to run the function on some perverse parameters that I specify and see what gets printed. Unit tests don't apply in those scenarios. – Mark Amery Mar 19 '13 at 18:04
  • 12
    To put that another way: unit tests are good tools for confirming that code works - or for detecting errors in places where you don't expect them to exist - with minimal human effort. They're *not* good tools for fiddling with code that you are currently building from scratch and already know to be unfinished or broken, and it's in that latter situation that I use the interactive interpreter. – Mark Amery Mar 19 '13 at 18:19
2

For Python 3.6+ you can use:

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
Mark Amery
  • 143,130
  • 81
  • 406
  • 459
machinekoder
  • 196
  • 1
  • 9
  • 1
    If I'm reading it correctly, this (by design) only reloads modules that are submodules of `m`. That's not quite what I was looking for back when I asked this question; if `m` imports modules that are its siblings or cousins, I wanted to import them too. But perhaps it'll be helpful to some other reader. – Mark Amery Jan 26 '19 at 16:03
  • Oh, yes you are right. I'm using it to develop a blender plugin, so external dependencies shouldn't be reloaded. – machinekoder Jan 27 '19 at 20:13
  • For a correct solution one would need to build up depdency tree between the loaded modules, else you will need to reload multiple times before everything is updated. – machinekoder Jan 27 '19 at 20:14
2

I found the idea to just clear all the modules and then reimport your module here, which suggested to just do this:

import sys
sys.modules.clear()

This would mess up modules loaded that you don't want to reload (if you only want to reload your own modules). My idea is to only clear the modules that include your own folders. Something like this:

import sys
import importlib

def reload_all():
    delete_folders = ["yourfolder", "yourotherfolder"]

    for module in list(sys.modules.keys()):
        if any(folder in module for folder in delete_folders):
            del sys.modules[module]

    # And then you can reimport the file that you are running.
    importlib.import_module("yourfolder.entrypoint")

Reimporting your entry point will reimport all of its imports since the modules were cleared and it's automatically recursive.

1

Technically, in each file you could put a reload command, to ensure that it reloads each time it imports

a.py:

def testa():
    print 'hi!'

b.py:

import a
reload(a)
def testb():
    a.testa()

Now, interactively:

import b
b.testb()
#hi!

#<modify a.py>

reload(b)
b.testb()
#hello again!
askewchan
  • 45,161
  • 17
  • 118
  • 134
  • 3
    Sure, I could do this, but then my actual code files would be ugly and inefficient. I don't mind using silly hacks when I'm playing in the interactive interpreter, but I'd prefer not to introduce a silly hack into every file in my code just to let me be a bit lazier when I'm in the interpreter. :) – Mark Amery Mar 19 '13 at 18:15
1

I found the answer of redsk very useful. I propose a simplified (for the user, not as code) version where the path to the module is automatically gathered and recursion works for an arbitrary number of levels. Everything is self-contained in a single function. Tested on Python 3.4. I guess for python 3.3 one must import reload from imp instead of ... from importlib. It also checks if the __file__ file is present, which might be false if the coder forgets to define an __init__.py file in a submodule. In such case, an exception is raised.

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__))
fnunnari
  • 159
  • 2
  • 11
  • A cautious -1 because you haven't explained any advantage of this over redsk's version. You say it's "simplified for the user" but your version and redsk's both allow the caller to just call `rreload(some_module)`; where's the simplification? Maybe there's some value here I'm not appreciating, but if so, you've hidden it well. – Mark Amery Jan 26 '19 at 15:47
  • @MarkAmery, long time has passed since then, but I think the two "advantages for the user" can be expressed as: 1. All needed imports are embedded in the call, and; 2. there is an exception thrown if the `__init__.py` file is missing. For the second, it means that the developer is informed and can fix the problem, rather than the sub-module being ignored. Of course, it maybe makes sense to merge the two alternatives in a single solution. – fnunnari Jan 27 '19 at 18:58
0

Below is the recursive reload function that I use, including a magic function for ipython/jupyter.

It does a depth-first search through all sub-modules and reloads them in the correct order of dependence.

import logging
from importlib import reload, import_module
from types import ModuleType
from IPython.core.magic import register_line_magic

logger = logging.getLogger(__name__)


def reload_recursive(module, reload_external_modules=False):
    """
    Recursively reload a module (in order of dependence).

    Parameters
    ----------
    module : ModuleType or str
        The module to reload.

    reload_external_modules : bool, optional

        Whether to reload all referenced modules, including external ones which
        aren't submodules of ``module``.

    """
    _reload(module, reload_external_modules, set())


@register_line_magic('reload')
def reload_magic(module):
    """
    Reload module on demand.

    Examples
    --------
    >>> %reload my_module
    reloading module: my_module

    """
    reload_recursive(module)


def _reload(module, reload_all, reloaded):
    if isinstance(module, ModuleType):
        module_name = module.__name__
    elif isinstance(module, str):
        module_name, module = module, import_module(module)
    else:
        raise TypeError(
            "'module' must be either a module or str; "
            f"got: {module.__class__.__name__}")

    for attr_name in dir(module):
        attr = getattr(module, attr_name)
        check = (
            # is it a module?
            isinstance(attr, ModuleType)

            # has it already been reloaded?
            and attr.__name__ not in reloaded

            # is it a proper submodule? (or just reload all)
            and (reload_all or attr.__name__.startswith(module_name))
        )
        if check:
            _reload(attr, reload_all, reloaded)

    logger.debug(f"reloading module: {module.__name__}")
    reload(module)
    reloaded.add(module_name)
Kris
  • 22,079
  • 3
  • 30
  • 35
0

Building on top of @machinekoder's answer above, I'd set the reloaded modules to sys and finally return the reloaded module m from sys like so:

def deep_reload(m: ModuleType):
    name = m.__name__  # get the name that is used in sys.modules
    name_ext = name.split('.')[0]  # support finding submodules 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):
        sys.modules[pkg] = importlib.reload(sys.modules[pkg])
    return sys.modules[name]
Bruck Wubete
  • 141
  • 2
  • 5
-1

It is a tricky thing to do - I have an working example in this answer: how to find list of modules which depend upon a specific module in python

Community
  • 1
  • 1
jsbueno
  • 99,910
  • 10
  • 151
  • 209
  • Haven't read and tried to understand the code, but I did try pasting the defs into the interpreter and calling reload_dependences on my module: `Traceback (most recent call last): File "", line 1, in File "", line 9, in reload_dependences ImportError: Cannot re-init internal module __main__` – Mark Amery Mar 20 '13 at 15:26
  • Well - I think the error message is hitning that it tried to relaod the "__main__" module itself - in this case, your interpreter session. Try pasting that code in a separate file that you import into the itnerpreter IIRC it was how I developed it. – jsbueno Mar 23 '13 at 03:10
  • Tried that. Same error. By the way, sorry for the, uh, slightly slow reply. – Mark Amery Jan 26 '19 at 15:09
  • You just did not come-up 6 years later to downvote the answer, did you? :-) BTW, automatically reloading is a common thing in big Python projects in this age - you should check whatever Flask and Django are using for their "debug mode". – jsbueno Jan 27 '19 at 03:03