How to reload a module's function in Python?

后端 未结 8 1756
遥遥无期
遥遥无期 2020-11-28 09:27

Following up on this question regarding reloading a module, how do I reload a specific function from a changed module?

pseudo-code:

from foo import b         


        
相关标签:
8条回答
  • 2020-11-28 09:34

    As of today, the proper way of doing this is:

    import sys, importlib
    importlib.reload(sys.modules['foo'])
    from foo import bar
    

    Tested on python 2.7, 3.5, 3.6.

    0 讨论(0)
  • 2020-11-28 09:35

    If you are the author of foo.py, you can write:

    with open('bar.py') as f: bar_code=compile(f.read(),'bar.py','exec')
    
    def bar(a,b):
        exec(bar_code)
    
    def reload_bar():
        global bar_code
        with open('bar.py') as f: bar_code=compile(f.read(),'bar.py','exec') 
    

    Then, in your pseudocode, reload if bar.py has changed. This approach is especially nice when bar lives in the same module as the code that wants to hot-reload it rather than the OP's case where it lives in a different module.

    0 讨论(0)
  • 2020-11-28 09:38

    This one also works.

    • Pros: it supports from ... import ... instead of module.my_function, and it does not require to write the module as a string.
    • Cons: it needs the module to be imported.
    # Import of my_function and module
    import my_utils
    from my_utils import my_function
    
    # Test:
    my_function()
    
    # For reloading:
    from importlib import reload
    reload(my_utils)
    from my_utils import my_function
    
    # Test again:
    my_function()
    
    0 讨论(0)
  • 2020-11-28 09:43

    You can't reload a method from a module but you can load the module again with a new name, say foo2 and say bar = foo2.bar to overwrite the current reference.

    Note that if bar has any dependencies on other things in foo or any other side effects, you will get into trouble. So while it works, it only works for the most simple cases.

    0 讨论(0)
  • 2020-11-28 09:50

    While reloading of functions is not a feature of the reload function, it is still possible. I would not recommend doing it in production, but here is how it works: The function you want to replace is a object somewhere in memory, and your code might hold many references to it (and not to the name of the function). But what that function actually does when called is not saved in that object, but in another object that, in turn, is referenced by the function object, in its attribute __code__. So as long as you have a reference to the function, you can update its code:

    Module mymod:

    from __future__ import print_function
    def foo():
        print("foo")
    

    Other module / python session:

    >>> import mymod
    >>> mymod.foo()
    foo
    >>> old_foo_ref = mymod.foo
    >>> # edit mymod.py to make function "foo" print "bar"
    >>> reload(mymod)
    <module 'mymod' from 'mymod.py'>
    >>> old_foo_ref()   # old reference is running old code
    foo
    >>> mymod.foo()     # reloaded module has new code
    bar
    >>> old_foo_ref.__code__ = mymod.foo.__code__
    >>> old_foo_ref()   # now the old function object is also running the new code
    bar
    >>> 
    

    Now, in case you do not have a reference to the old function (i.e. a lambda passed into another function), you can try to get a hold of it with the gc module, by searching the list of all objects:

    >>> def _apply(f, x):
    ...     return lambda: f(x)
    ... 
    >>> tmp = _apply(lambda x: x+1, 1)  # now there is a lambda we don't have a reference to
    >>> tmp()
    2
    >>> import gc
    >>> lambdas = [obj for obj in gc.get_objects() if getattr(obj,'__name__','') == '<lambda>'] # get all lambdas
    [<function <lambda> at 0x7f315bf02f50>, <function <lambda> at 0x7f315bf12050>]     
    # i guess the first one is the one i passed in, and the second one is the one returned by _apply
    # lets make sure:
    >>> lambdas[0].__code__.co_argcount
    1  # so this is the "lambda x: ..."
    >>> lambdas[1].__code__.co_argcount
    0  # and this is the "lambda: ..."
    >>> lambdas[0].__code__ = (lambda x: x+2).__code__  # change lambda to return arg + 2
    >>> tmp()
    3  # 
    >>> 
    
    0 讨论(0)
  • 2020-11-28 09:55

    You will have to use reload to reload the module, since you can't reload just the function:

    >>> import sys
    >>> reload(sys)
    <module 'sys' (built-in)>
    >>>
    
    0 讨论(0)
提交回复
热议问题