How CPython Works Under the Hood
A Deep Dive into the Python Execution Engine (For Backend & DevOps Engineers)
Why You Should Care
When you run:
print("Hello, World")You are not “running Python.”
You are running CPython — the reference implementation of Python written in C.
Understanding how CPython works internally helps you:
- Debug performance bottlenecks
- Optimize memory usage
- Design better concurrency models
- Make smarter scaling decisions in Kubernetes
- Evaluate runtime upgrades confidently
This post breaks down the entire execution lifecycle — from source code to machine instructions — with diagrams and production relevance.
🧠 What Is CPython?
CPython is the default and most widely used implementation of Python.
It is:
- Written in C
- Interpreted (bytecode-based VM)
- Reference-counted memory managed
- GIL-protected (in standard builds)
Other implementations exist (PyPy, Jython), but when you type python, you're almost certainly running CPython.
🔄 High-Level Execution Flow
Here is the simplified lifecycle when you run a Python file:
Step-by-step:
- Source Code (.py)
- Lexer & Parser
- AST (Abstract Syntax Tree)
- Bytecode Compilation
- Python Virtual Machine (PVM)
- Execution via Evaluation Loop
- Memory Management (Ref Counting + GC)
Let’s go deeper into each stage.
1️⃣ Lexing & Parsing
When CPython reads your code:
x = 10 + 20It first tokenizes it:
- NAME (
x) - EQUALS
- NUMBER (
10) - PLUS
- NUMBER (
20)
Then builds an AST (Abstract Syntax Tree).
Inspecting the AST
import ast
code = "x = 10 + 20"
tree = ast.parse(code)
print(ast.dump(tree, indent=4))This produces a tree-like structure representing program logic.
Why This Matters
- Linters operate on AST
- Security scanners analyze AST
- Code transformation tools use AST
- Static analyzers depend on this layer
For DevOps pipelines, AST-based tools power:
- SAST scanning
- Policy enforcement
- Code quality gates
2️⃣ Compilation to Bytecode
The AST is compiled into bytecode — instructions for the Python Virtual Machine.
You can inspect bytecode using dis.
import dis
def add(a, b):
return a + b
dis.dis(add)Example output:
LOAD_FAST 0 (a)
LOAD_FAST 1 (b)
BINARY_OP +
RETURN_VALUEThis is not machine code.
It is CPython bytecode, executed by the evaluation loop.
3️⃣ The Python Virtual Machine (PVM)
The PVM is a stack-based virtual machine.
Unlike register-based VMs (like JVM), CPython:
- Pushes operands to stack
- Performs operations
- Pops results
Example:
For a + b:
- Push
a - Push
b - Execute
BINARY_OP - Push result
4️⃣ The Evaluation Loop (The Heart of CPython)
The most critical file in CPython source:
Python/ceval.cThis contains the evaluation loop.
Simplified version:
while (true) {
opcode = next_instruction();
switch (opcode) {
case LOAD_FAST:
...
case BINARY_OP:
...
case RETURN_VALUE:
...
}
}This loop:
- Fetches bytecode
- Decodes opcode
- Executes corresponding C logic
- Moves to next instruction
🔥 Modern Optimization: Adaptive Interpreter
In modern Python versions:
Instead of generic opcodes:
LOAD_ATTRCPython dynamically specializes to:
LOAD_ATTR_INSTANCE_VALUEThis reduces:
- Dictionary lookups
- Attribute resolution overhead
- Cache misses
This makes CPython faster without full JIT compilation.
5️⃣ Frame Objects & Call Stack
Each function call creates a frame object.
A frame contains:
- Local variables
- Reference to code object
- Stack
- Instruction pointer
- Link to previous frame
You can inspect frames:
import inspect
def demo():
frame = inspect.currentframe()
print(frame.f_locals)
demo()Production Impact
Heavy function nesting:
- Increases frame creation
- Increases memory churn
- Affects async-heavy apps
Modern Python reduces unnecessary frame allocation.
6️⃣ Object Model Internals
Everything in Python is an object.
Get DevOpsByte’s stories in your inbox
Join Medium for free to get updates from this writer.
Internally:
Every object begins with:
typedef struct {
Py_ssize_t ob_refcnt;
PyTypeObject *ob_type;
} PyObject;Every Python object contains:
- Reference count
- Type pointer
This is why:
a = 10
b = aDoes not copy the integer — it increases refcount.
7️⃣ Memory Management
CPython uses:
1️⃣ Reference Counting
Every object tracks how many references point to it.
When count reaches zero → object is freed.
2️⃣ Generational Garbage Collector
Handles cyclic references:
a = []
a.append(a)Reference counting alone cannot free this.
GC periodically scans generations.
Memory Allocation (pymalloc)
CPython uses a custom allocator:
- Optimized for small objects
- Reduces system calls
- Minimizes fragmentation
DevOps Insight
In containers:
- Fragmentation increases RSS
- High RSS leads to OOMKill
- Memory profiling becomes critical
8️⃣ The Global Interpreter Lock (GIL)
CPython uses a Global Interpreter Lock.
Only one thread executes Python bytecode at a time.
Why?
Because:
- Reference counting is not thread-safe
- Removing GIL requires atomic operations
Effect:
- CPU-bound tasks don’t scale with threads
- I/O-bound tasks scale well
DevOps Scaling Strategy:
9️⃣ Import System Internals
When you run:
import mathCPython:
- Checks
sys.modules - Searches
sys.meta_path - Loads module
- Compiles to bytecode
- Caches
.pyc
The import system is extensible.
This is how:
- Plugin systems work
- Dynamic loaders operate
- Zip imports function
🔟 Bytecode Caching (.pyc Files)
Python creates .pyc files in __pycache__.
Benefits:
- Faster startup
- Reduced re-compilation
In Docker:
- Pre-compiling reduces cold start time
- But increases image size slightly
1️⃣1️⃣ Async Internals
Async functions:
async def fetch():
...Are compiled into:
- Coroutine objects
- Scheduled on event loop
- Managed via state machine
Underneath:
- Await points save frame state
- Resume execution later
This is why async avoids OS threads.
🐳 How All This Matters in Kubernetes
Understanding CPython internals helps with:
1️⃣ Pod Sizing
- Memory model → define correct limits
- Avoid GC pauses
- Prevent OOM
2️⃣ Horizontal Scaling
- GIL limits CPU-bound scaling
- Multiple workers required
3️⃣ Cold Start Optimization
- Import cost
- Bytecode caching
- Startup frame creation
🔬 Real-World Example
Consider:
FastAPI service
- 8 cores
- 2GB RAM
- High JSON serialization
Knowing:
- Frame cost
- Exception path optimization
- Attribute specialization
Helps tune:
- Worker count
- Thread vs process
- Memory limits
🎯 Key Takeaways
CPython:
- Is a stack-based virtual machine
- Executes bytecode via evaluation loop
- Uses reference counting + generational GC
- Is protected by GIL (standard build)
- Dynamically specializes bytecode
- Uses frame objects for call stack
- Optimizes memory via pymalloc
Understanding this lets you:
- Debug performance scientifically
- Optimize architecture decisions
- Avoid cargo-cult scaling
- Write production-grade Python systems








