🧠 Python DeepCuts — 💡 Import Cache & sys.modules
Posted on: April 15, 2026
Description:
One of the most misunderstood behaviours in Python is how imports actually work.
You write:
import math
And it feels like Python loads the module every time.
But in reality, Python loads a module only once, then stores it in a global cache called sys.modules.
This DeepCut explains how that cache works — and why it can lead to subtle bugs in real systems.
🧩 Modules Are Cached in sys.modules
When a module is imported, Python stores it in:
sys.modules
import sys
import math
"math" in sys.modules
This is a dictionary:
{
"module_name": module_object
}
From that point onward, Python uses this cached object instead of reloading the module.
🧠 Import Happens Only Once
If you import the same module multiple times:
import math
import math
Python does not execute the module again.
Instead:
- it checks sys.modules
- finds the existing module
- returns the same object
id(math) == id(sys.modules["math"])
This is why imports are fast after the first load.
🔄 Module Code Runs Only Once
Top-level code inside a module executes only during the first import.
# demo_module.py
print("Executing module")
import demo_module
import demo_module
Output:
Executing module
Printed only once.
This behaviour is essential for:
- performance
- avoiding repeated side effects
- maintaining module-level state
🧠 Reloading a Module
You can force Python to re-execute a module using:
import importlib
importlib.reload(module)
This reloads the module’s code — but with an important limitation.
⚠️ Reload Does NOT Update Existing References
Consider:
from demo_module import some_function
importlib.reload(demo_module)
Here:
- demo_module is updated
- but some_function still points to the old version
This happens because:
- the reference was copied earlier
- reload does not rewrite existing references
This is a common source of bugs in:
- Jupyter notebooks
- hot-reload systems
- long-running services
🧬 Circular Imports and Partial Initialisation
When two modules import each other, Python avoids infinite recursion by:
- inserting the module into sys.modules before execution finishes
This means:
- other modules may access it
- even if it’s not fully initialised
This leads to errors like:
- missing attributes
- partially defined objects
Understanding this behaviour is key to debugging circular imports.
🔍 sys.modules Is the Source of Truth
Python fully trusts sys.modules.
You can even modify it manually:
import sys
sys.modules["fake_module"] = "Hello"
import fake_module
print(fake_module)
Python does not validate this — it simply returns what’s stored.
This makes sys.modules extremely powerful — and potentially dangerous.
🧠 Why This Matters in Real Systems
Understanding module caching explains many real-world issues:
- Why changes don’t reflect after reload
- Why imports behave inconsistently in notebooks
- Why circular imports break unexpectedly
- Why shared state exists across modules
- How plugin systems dynamically load code
This is not just theory — it affects production systems directly.
⚠️ Common Pitfalls
- Using from module import x with reload
- Relying on module re-execution
- Ignoring circular import risks
- Assuming imports are stateless
- Modifying sys.modules unintentionally
These issues often appear only in complex or long-running systems.
✅ Key Points
- Python caches modules in sys.modules
- Imports reuse the same module object
- Module code runs only once
importlib.reloadre-executes code but has limitations- Existing references are not updated on reload
- Circular imports can expose partially initialised modules
The import system is optimised for performance — but requires careful understanding.
Code Snippet:
import sys
import importlib
import math
print("math in cache:", "math" in sys.modules)
print("Module object:", sys.modules["math"])
print(id(math), id(sys.modules["math"]))
# Example with custom module
# Create demo_module.py with:
# print("Module executing")
import demo_module
import demo_module
importlib.reload(demo_module)
from demo_module import some_function
importlib.reload(demo_module)
# some_function still refers to old version
# sys.modules manipulation
sys.modules["fake_module"] = "Hello"
import fake_module
print(fake_module)
No comments yet. Be the first to comment!