Search code examples
pythonpython-internalspython-3.12

Where did sys.modules go?


>>> 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.


Solution

  • 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.