I was experimenting with setting the dictionary sys.modules while working on an answer to another question and came across something interesting. The linked question deals with removing all the effects of importing a module. Based on another post, I came up with the idea of deleting all new modules from sys.modules after an import. My initial implementation was to do the following (testing with numpy as the module to load and unload):
# Load the module
import sys
mod_copy = sys.modules.copy()
print('numpy' in mod_copy, 'numpy' in sys.modules) # False False
import numpy
print('numpy' in mod_copy, 'numpy' in sys.modules) # False True
print(id(numpy)) # 45138472
The printouts show that numpy was imported successfully and that the shallow copy does not contain it, as expected.
Now my idea was to unload the module by swapping mod_copy back into sys.modules, then delete the local reference to the module. That should in theory remove all references to it (and possibly it does):
sys.modules = mod_copy
del numpy
print('numpy' in sys.modules) # False
This should be enough to be able to re-import the module, but when I do
import numpy
print('numpy' in sys.modules) # False
print(id(numpy)) # 45138472
It appears that the numpy module is not reloaded since it has the same id as before. It does not show up in sys.modules, despite the fact that the import statement raises no errors and appears to complete successfully (i.e., a numpy module exists in the local namespace).
On the other hand, the implementation that I made in my answer to the linked question does appear to work fine. It modifies the dictionary directly instead of swapping it out:
import sys
mod_copy = sys.modules.copy()
print('numpy' in mod_copy, 'numpy' in sys.modules) # False False
import numpy
print('numpy' in mod_copy, 'numpy' in sys.modules) # False True
print(id(numpy)) # 35963432
for m in list(sys.modules):
    if m not in mod_copy:
        del sys.modules[m]
del numpy
print('numpy' in sys.modules) # False
import numpy
print('numpy' in sys.modules) # True
print(id(numpy)) # (54941000 != 35963432)
I am using Python 3.5.2 on an Anaconda install. I am most interested in explanations focusing on Python 3, but I am curious about Python 2.7+ as well.
The only thing I can think of that is happening here is that sys maintains another reference to sys.modules and uses that internal reference regardless of what I do to the public one. I am not sure that this covers everything though, so I would like to know what is really going on.
 
     
    