Dynamically reload class definition in Python
I wrote an IRC bot using Twisted and now I got to the point where I want to dynamically reload the functionality.
In my main program I am doing from bots.google import GoogleBot
and I have looked at how to use reload
to reload modules, but I still cannot figure out how to do dynamic re-import of classes.
So, given a Python class, how do I dynamically reload a class definition?
source to share
I got it, here is the code I'm using:
def reimport_class(self, cls):
"""
Reload and reimport class "cls". Return the new definition of the class.
"""
# Get the fully qualified name of the class.
from twisted.python import reflect
full_path = reflect.qual(cls)
# Naively parse the module name and class name.
# Can be done much better...
match = re.match(r'(.*)\.([^\.]+)', full_path)
module_name = match.group(1)
class_name = match.group(2)
# This is where the good stuff happens.
mod = __import__(module_name, fromlist=[class_name])
reload(mod)
# The (reloaded definition of the) class itself is returned.
return getattr(mod, class_name)
source to share
Reboot is unreliable and has many corner cases where it can fail. It is suitable for reloading simple, stand-alone scripts. If you want to dynamically reload your code without restarting, use forkloop instead:
http://opensourcehacker.com/2011/11/08/sauna-reload-the-most-awesomely-named-python-package-ever/
source to share
When you execute from ... import ...
, it binds the object to the local namespace, so whatever you need it re-imports. However, since the module is already loaded, it just re-imports the same version of the class so that you also have to reload the module. So this should do it:
from bots.google import GoogleBot
...
# do stuff
...
reload(bots.google)
from bots.google import GoogleBot
If for some reason you don't know the name of the module, you can get it from GoogleBot. module .
source to share
You cannot reload a module using reload(module)
when using a form from X import Y
. In this case, you need to do something like reload(sys.modules['module'])
.
This may not be the best way to do what you want, but it works!
import bots.google
class BotClass(irc.IRCClient):
def __init__(self):
global plugins
plugins = [bots.google.GoogleBot()]
def privmsg(self, user, channel, msg):
global plugins
parts = msg.split(' ')
trigger = parts[0]
if trigger == '!reload':
reload(bots.google)
plugins = [bots.google.GoogleBot()]
print "Successfully reloaded plugins"
source to share
You can use sys.modules
to dynamically reload modules based on user input.
Say you have a folder with multiple plugins, for example:
module/
cmdtest.py
urltitle.py
...
You can use sys.modules
this way to load / reload modules based on userinput:
import sys
if sys.modules['module.' + userinput]:
reload(sys.modules['module.' + userinput])
else:
' Module not loaded. Cannot reload '
try:
module = __import__("module." + userinput)
module = sys.modules["module." + userinput]
except:
' error when trying to load %s ' % userinput
source to share
def reload_class(class_obj):
module_name = class_obj.__module__
module = sys.modules[module_name]
pycfile = module.__file__
modulepath = string.replace(pycfile, ".pyc", ".py")
code=open(modulepath, 'rU').read()
compile(code, module_name, "exec")
module = reload(module)
return getattr(module,class_obj.__name__)
There are many mistakes you can make about this, if you use globals you may have to figure out what happens next.
source to share