So, I know I can load a python module given a filepath to a module as such
def import_module_from_fpath(module_fpath):
from os.path import basename, splitext, isdir, join, exists, dirname, split
import platform
if isdir(module_fpath):
module_fpath = join(module_fpath, '__init__.py')
print('module_fpath = {!r}'.format(module_fpath))
if not exists(module_fpath):
raise ImportError('module_fpath={!r} does not exist'.format(
module_fpath))
python_version = platform.python_version()
modname = splitext(basename(module_fpath))[0]
if modname == '__init__':
modname = split(dirname(module_fpath))[1]
if python_version.startswith('2.7'):
import imp
module = imp.load_source(modname, module_fpath)
elif python_version.startswith('3'):
import importlib.machinery
loader = importlib.machinery.SourceFileLoader(modname, module_fpath)
module = loader.load_module()
# module = loader.exec_module(modname)
else:
raise AssertionError('invalid python version={!r}'.format(
python_version))
return module
However, this always seems to populate an entry in sys.modules?
I have the case where I have two different modules in different paths. Both modules are not reachable from the PYTHONPATH. I want to be able to load these modules locally in different functions and not have them interfere with each other. The kicker to this problem is both modules have the same name (different versions). Thus, I don't want sys.modules to cache anything in between.
I'm not sure if its safe to simply delete the module name from sys.modules after I load them. It seems like it might be from a quick test I did:
import shutil
import ubelt as ub
test_root = ub.ensure_app_cache_dir('test_fpath_import')
# Clear the directory
shutil.rmtree(test_root)
test_root = ub.ensure_app_cache_dir('test_fpath_import')
# -----
# Define two temporary modules with the same name that are not in sys.path
import sys, os, os.path
from os.path import join
# Even though they have the same name they have different values
mod1_fpath = ub.ensuredir((test_root, 'path1', 'testmod'))
ub.writeto(join(mod1_fpath, '__init__.py'), 'version = 1\nfrom . import sibling\na1 = 1')
ub.writeto(join(mod1_fpath, 'sibling.py'), 'spam = \"ham\"\nb1 = 2')
# Even though they have the same name they have different values
mod2_fpath = ub.ensuredir((test_root, 'path2', 'testmod'))
ub.writeto(join(mod2_fpath, '__init__.py'), 'version = 2\nfrom . import sibling\na2 = 3')
ub.writeto(join(mod2_fpath, 'sibling.py'), 'spam = \"jam\"\nb2 = 4')
# -----
# Neither module should be importable through the normal mechanism
try:
import testmod
assert False, 'should fail'
except ImportError as ex:
pass
mod1 = import_module_from_fpath(mod1_fpath)
print('mod1.version = {!r}'.format(mod1.version))
print('mod1.version = {!r}'.format(mod1.version))
print(mod1.version == 1, 'mod1 version is 1')
print('mod1.a1 = {!r}'.format(mod1.a1))
mod2 = import_module_from_fpath(mod2_fpath)
print('mod2.version = {!r}'.format(mod2.version))
print(mod2.version == 2, 'mod2 version is 2')
print('mod2.a2 = {!r}'.format(mod1.a2))
# BUT Notice how mod1 is mod2
print(mod1 is mod2)
# mod1 has attributes from mod1 and mod2
print('mod1.a1 = {!r}'.format(mod1.a1))
print('mod1.a2 = {!r}'.format(mod1.a2))
print('mod2.a1 = {!r}'.format(mod2.a1))
print('mod2.a2 = {!r}'.format(mod2.a2))
# Both are version 2
print('mod1.version = {!r}'.format(mod1.version))
print('mod2.version = {!r}'.format(mod2.version))
# However sibling always remains at version1 (ham)
print('mod2.sibling.spam = {!r}'.format(mod2.sibling.spam))
# now importing testmod works because it reads from sys.modules
import testmod
# reloading mod1 overwrites attrs again
mod1 = ut.import_module_from_fpath(mod1_fpath)
# Removing both from sys.modules
del sys.modules['testmod']
del sys.modules['testmod.sibling']
mod2 = ut.import_module_from_fpath(mod2_fpath)
print(not hasattr(mod2, 'a1'),
'mod2 no longer has a1 and it reloads itself correctly')
The above script illustrates what happens when trying to load two modules different modules with the same name: They overwrite / complement each other. However, if I do this import and then directly delete all imported modules it seems to do the "right" thing.
My questions are:
Is it really doing the "right" thing? Or is there some case that my script doesn't test for.
Is there a way to import a module locally without it being added to
sys.modules(aka the global namespace)? (issys.modulesthe only thing that makes up the global namespace for modules?)
(I only have tested the above code in Python 3, not sure if the old imp modules does the same thing as importlib).