Reload imported modules in imported module

I’ve posted this before, but here is my favorite way of handling this by far!
Python stores all imports in a dictionary sys.modules. So if you remove an entry from there, it’s like it was never imported in the first place!
Now, of course, this can be dangerous (you could technically unload built-in stuff). But with proper filtering and edge case handling (which we’ve handled already!) we almost never have to restart maya to get code updates out to our artists.

One final note: If you’re doing anything with Qt (or any other non-maya-builtin ui), then you need to close the window you’re working on before you remove the entries from sys.modules. Our implementation emits a Qt signal that all of our UI’s connect to so it auto-closes the windows, but that’s the one thing you’ll have to handle yourself if you decide to use this.

import os, sys

def clearPathSymbols(paths, keepers=None):
	"""
	Removes path symbols from the environment.

	This means I can unload my tools from the current process and re-import them
	rather than dealing with the always finicky reload()

	I use directory paths rather than module names because it gives me more control
	over what is unloaded

	*Make sure to close any UI's you're clearing before using this function*

	Parameters
	----------
	paths : list
		List of directory paths that will have their modules removed
	keepers : list, optional
		List of module names that will not be removed
	"""

	## TODO ## Possibly emit a signal to close my custom UI's
	keepers = keepers or []
	paths = [os.path.normcase(os.path.normpath(p)) for p in paths]

	for key, value in sys.modules.items():
		protected = False

		# Used by multiprocessing library, don't remove this.
		if key == '__parents_main__':
			protected = True

		# Protect submodules of protected packages
		if key in keepers:
			protected = True

		ckey = key
		while not protected and '.' in ckey:
			ckey = ckey.rsplit('.', 1)[0]
			if ckey in keepers:
				protected = True

		if protected:
			continue

		try:
			packPath = value.__file__
		except AttributeError:
			continue

		packPath = os.path.normcase(os.path.normpath(packPath))

		isEnvPackage = any(packPath.startswith(p) for p in paths)
		if isEnvPackage:
			sys.modules.pop(key)

With that function defined, you could do something like this:

clearPathSymbols([r'drive:\path\to\myModule', r'drive:\path\to\some\other\submodule'])
import myModule
5 Likes