How to reload a module's function in Python?
Asked Answered
L

8

65

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 bar

if foo.py has changed:
    reload bar
Levitan answered 1/9, 2011 at 13:34 Comment(0)
M
15

What you want is possible, but requires reloading two things... first reload(foo), but then you also have to reload(baz) (assuming baz is the name of the module containing the from foo import bar statement).

As to why... When foo is first loaded, a foo object is created, containing a bar object. When you import bar into the baz module, it stores a reference to bar. When reload(foo) is called, the foo object is blanked, and the module re-executed. This means all foo references are still valid, but a new bar object has been created... so all references that have been imported somewhere are still references to the old bar object. By reloading baz, you cause it to reimport the new bar.


Alternately, you can just do import foo in your module, and always call foo.bar(). That way whenever you reload(foo), you'll get the newest bar reference.

NOTE: As of Python 3, the reload function needs to be imported first, via from importlib import reload

Midstream answered 1/9, 2011 at 17:51 Comment(3)
problem is it will complain that there is no foo, since it never got imported directly.Swirl
Need to import foo first, then reload it, then reimporting bar worksSplashboard
@Splashboard no need to import foo, actually. See my answer below.Imputable
I
89

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.

Imputable answered 18/10, 2017 at 15:37 Comment(4)
This answer should be marked as the accepted answer these days, since although the original accepted answer is theoretically correct this one is the practical answer and supports newer versions of pythonChigetai
@Antony Hatchkins If foo wasn't previously loaded, it raises an error. Would you mind to edit and use the following? importlib.reload(sys.modules.get('foo', sys))Fernald
@Fernald yes, it might be useful in some cases, but I'd rather leave it the way it currently is as it is shorter and immediately clear for a reader looking for a quick answer.Imputable
not work for me, got error: AttributeError: 'module' object has no attribute 'reload'Whitsuntide
M
15

What you want is possible, but requires reloading two things... first reload(foo), but then you also have to reload(baz) (assuming baz is the name of the module containing the from foo import bar statement).

As to why... When foo is first loaded, a foo object is created, containing a bar object. When you import bar into the baz module, it stores a reference to bar. When reload(foo) is called, the foo object is blanked, and the module re-executed. This means all foo references are still valid, but a new bar object has been created... so all references that have been imported somewhere are still references to the old bar object. By reloading baz, you cause it to reimport the new bar.


Alternately, you can just do import foo in your module, and always call foo.bar(). That way whenever you reload(foo), you'll get the newest bar reference.

NOTE: As of Python 3, the reload function needs to be imported first, via from importlib import reload

Midstream answered 1/9, 2011 at 17:51 Comment(3)
problem is it will complain that there is no foo, since it never got imported directly.Swirl
Need to import foo first, then reload it, then reimporting bar worksSplashboard
@Splashboard no need to import foo, actually. See my answer below.Imputable
S
8

Hot reloading is not something you can do in Python reliably without blowing up your head. You literally cannot support reloading without writing code special ways, and trying to write and maintain code that supports reloading with any sanity requires extreme discipline and is too confusing to be worth the effort. Testing such code is no easy task either.

The solution is to completely restart the Python process when code has changed. It is possible to do this seamlessly, but how depends on your specific problem domain.

Slating answered 1/9, 2011 at 13:43 Comment(2)
All of the other, related questions, and the reload docks, basically say the same thing.Biblicist
Not sure I agree with that. importlib.reload() works fine and is great for dev. Just have to know how it works, haven't had any problems with it.Bechuana
D
4

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()
Dismount answered 18/10, 2020 at 23:36 Comment(0)
D
3

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  # 
>>> 
Dentilingual answered 2/3, 2015 at 21:34 Comment(0)
P
2

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)>
>>>
Prelect answered 1/9, 2011 at 13:42 Comment(5)
If in that code OP did reload(foo), bar would not change. What would happen is subtle and confusing, so it's best simply never to use reload or to attempt to do hot reloading of code in Python. Your sanity and reliability will thank you.Slating
Did you test this? Does it change exit if you from sys import exit then change sys.exit then import sys and reload(sys)?Biblicist
(Note that in the example of the built-in sys this isn't even possible.)Slating
but I didn't say from ... import ...!Prelect
@rm, OP did! And even if he hadn't, reload is not helpful advice.Slating
C
2

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.

Castora answered 1/9, 2011 at 13:58 Comment(0)
M
1

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.

Ministrant answered 11/10, 2015 at 18:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.