>>> import sys
>>> del sys.modules['sys']
>>> import sys
>>> sys.modules
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: module 'sys' has no attribute 'modules'
Why does re-imported sys
module not have some attributes anymore?
I am using Python 3.12.3 and it happens in macOS, Linux, and Windows. It happens in both the REPL and in a .py script. It does not happen in Python 3.11.
This is pretty obviously something you shouldn't do, naturally liable to break things. It happens to break things this particular way on the Python implementation you tried, but Python doesn't promise what will happen. Most of what I am about to say is implementation details.
The sys
module cannot be initialized like normal built-in modules, as it's responsible for so much core functionality. Instead, on interpreter startup, Python creates the sys
module with the special function _PySys_Create
. This function is responsible for (part of the job of) correctly setting up the sys
module, including the sys.modules
attribute:
if (PyDict_SetItemString(sysdict, "modules", modules) < 0) {
goto error;
}
When you do del sys.modules['sys']
, the import system loses track of the sys
module. When you try to import it again, Python tries to create an entirely new sys
module, and it does so as if sys
were an ordinary built-in module. It goes through the procedure for initializing ordinary built-in modules. This procedure leaves the new sys
module in an inconsistent, improperly initialized state, as sys
was never designed to be initialized this way.
There is support for reloading sys
, although I believe the dev team is thinking of taking this support out - the use cases are very obscure, and the only one I can think of off the top of my head is obsolete. Part of the reinitialization ends up hitting a code path intended for reloading sys
, which updates its __dict__
from a copy created early in the original initialization of sys
, right before sys.modules
is set:
interp->sysdict_copy = PyDict_Copy(sysdict);
if (interp->sysdict_copy == NULL) {
goto error;
}
if (PyDict_SetItemString(sysdict, "modules", modules) < 0) {
goto error;
}
This copy is handled differently on earlier Python versions, hence the version-related behavior differences.