# 5. The Runtime Model

# 5. The Runtime Model

The CPython runtime is the machinery that exists after the process starts and before Python code finishes executing. It owns interpreter state, thread state, modules, builtins, memory allocators, exception state, import state, frames, pending calls, signal handling, and shutdown behavior.

A Python program appears to run as a sequence of statements. CPython runs it inside a layered runtime system.

```text
operating system process
    CPython runtime
        interpreter state
            thread state
                frame stack
                    executing code object
                        bytecode instructions
                            object operations
```

This hierarchy is the main map for understanding execution.

## 5.1 Process, Runtime, Interpreter, Thread, Frame

A running CPython program has several nested execution units.

| Unit         | Meaning                                                              |
| ------------ | -------------------------------------------------------------------- |
| Process      | The OS process containing the CPython executable or embedded runtime |
| Runtime      | Global CPython state shared across the process                       |
| Interpreter  | An isolated Python interpreter state inside the runtime              |
| Thread state | Per-thread execution state for one interpreter                       |
| Frame        | One active execution context                                         |
| Code object  | Compiled bytecode and metadata                                       |
| Object       | Runtime value manipulated by bytecode                                |

A simple script:

```python
x = 1
print(x + 2)
```

runs inside a frame. That frame belongs to a thread state. The thread state belongs to an interpreter. The interpreter belongs to the CPython runtime.

Conceptually:

```text
_PyRuntimeState
    PyInterpreterState
        PyThreadState
            PyFrameObject or internal frame
                PyCodeObject
                locals
                globals
                builtins
                value stack
```

The exact C structs change across versions, but the hierarchy is stable enough to guide source reading.

## 5.2 The Runtime

The runtime is process-wide CPython state.

It includes global services used by one or more interpreters:

```text
runtime initialization state
memory allocator state
interpreter list
GIL state
pending calls
signal handling state
audit hook state
global caches
runtime finalization flags
```

At a high level, the runtime answers:

```text
Has CPython been initialized?
Which interpreters exist?
Is the runtime finalizing?
Which global locks and services are active?
Which process-wide hooks are installed?
```

This matters most during startup, shutdown, embedding, subinterpreters, and free-threading work.

A normal Python script usually has one runtime and one main interpreter.

## 5.3 Interpreter State

An interpreter state represents one Python interpreter inside the process.

It owns language-level state such as:

```text
modules dictionary
builtins
import state
sys module state
codec state
warnings state
garbage collector state
interned strings
per-interpreter caches
execution configuration
```

Conceptually:

```text
PyInterpreterState
    modules
    builtins
    sysdict
    import machinery
    gc generations
    codec registry
    pending async exception state
```

Most Python programs use only the main interpreter.

Subinterpreters create additional interpreter states inside the same process. They can have separate module dictionaries and runtime state, but they still share some process-level resources.

## 5.4 Thread State

Each OS thread that executes Python code needs a thread state.

A thread state stores execution information for one thread in one interpreter:

```text
current frame
current exception state
recursion depth
profiling function
tracing function
async exception request
thread-local interpreter data
```

A thread state connects native execution to Python execution.

Conceptually:

```text
current OS thread
    PyThreadState
        current interpreter
        current frame
        exception information
        tracing and profiling hooks
```

When C code needs to raise an exception, access the current frame, or interact with Python APIs, it often needs the current thread state.

## 5.5 Frames

A frame is an execution record.

CPython creates a frame when it executes a module body, function body, class body, generator, coroutine, or comprehension.

A frame contains:

```text
code object
globals dictionary
builtins dictionary
locals storage
value stack
instruction pointer
block and exception state
line number state
owner information
```

For a function:

```python
def add(a, b):
    c = a + b
    return c
```

calling `add(2, 3)` creates a frame for that call.

The frame stores:

```text
code object for add
a = 2
b = 3
c after assignment
temporary stack values
current bytecode offset
globals of the defining module
builtins visible to the function
```

A frame is the concrete runtime object that makes a function call active.

## 5.6 Code Objects

A code object is compiled executable content.

It is immutable metadata plus bytecode. It does not store the current values of local variables.

For example:

```python
def f(x):
    y = x + 1
    return y
```

The function object has a code object:

```python
code = f.__code__

print(code.co_name)
print(code.co_varnames)
print(code.co_consts)
```

The code object says how to execute the function. The frame stores one active execution of that code.

This distinction matters:

```text
code object: reusable compiled program
frame: one running invocation of that program
function: callable object that wraps code with globals, defaults, and closure
```

Many calls to the same function reuse the same code object but create separate frames.

## 5.7 Function Objects

A Python function object wraps a code object with runtime context.

It contains:

```text
code object
globals dictionary
defaults
keyword-only defaults
closure cells
annotations
function name
qualified name
module name
dict for custom attributes
```

For example:

```python
x = 10

def f(y):
    return x + y
```

The function `f` needs more than bytecode. It also needs the globals dictionary where `x` can be found.

Conceptually:

```text
PyFunctionObject
    func_code      code for f
    func_globals   module globals
    func_defaults  default argument values
    func_closure   captured cells
```

Calling the function creates a frame using these components.

## 5.8 Objects and Types

At runtime, bytecode manipulates object references.

Every Python value is an object:

```python
42
"hello"
[1, 2, 3]
{"a": 1}
lambda x: x + 1
Exception("bad")
```

Each object has a type.

The type determines behavior:

```text
allocation
deallocation
attribute lookup
method lookup
call behavior
numeric operations
sequence operations
mapping operations
iteration
representation
hashing
comparison
```

For example:

```python
len(x)
```

does not directly inspect every possible object layout. It asks the object’s type how length works.

This is why the runtime model depends on type objects. Bytecode instructions are generic. Type slots provide concrete behavior.

## 5.9 Namespaces

Python execution uses namespaces.

The main namespace categories are:

| Namespace         | Backing storage                                   |
| ----------------- | ------------------------------------------------- |
| Locals            | Function fast locals, class namespace, or mapping |
| Globals           | Module dictionary                                 |
| Builtins          | Builtins dictionary                               |
| Object attributes | Instance dict, slots, descriptors, type lookup    |
| Module attributes | Module dictionary                                 |

For this code:

```python
x = 1

def f():
    return x + len([1, 2])
```

inside `f`, CPython resolves:

```text
x     global name in module dictionary
len   builtin name if not found in globals
[1,2] newly created list object
```

Name resolution depends on compile-time classification and runtime namespace lookup.

## 5.10 Fast Locals

Function local variables are usually stored in an optimized array-like layout, not a normal dictionary lookup for every access.

For a function:

```python
def f(a, b):
    c = a + b
    return c
```

the compiler knows the local names:

```text
a
b
c
```

The frame can store them in indexed slots.

Bytecode can then use fast local operations:

```text
LOAD_FAST a
LOAD_FAST b
STORE_FAST c
```

This is faster than dictionary lookup.

A locals dictionary may be materialized when needed, for example by `locals()`, tracing, debugging, or frame inspection. But the normal execution path uses fast locals.

## 5.11 The Value Stack

CPython bytecode uses a stack machine.

Instructions push and pop temporary values from the frame’s value stack.

For:

```python
z = x + y
```

the conceptual execution is:

```text
LOAD_FAST x       push x
LOAD_FAST y       push y
BINARY_OP +       pop x and y, push result
STORE_FAST z      pop result into local z
```

The value stack is temporary execution storage. It is separate from local variables.

A frame therefore contains both:

```text
local variable storage
value stack for intermediate operations
```

This explains why bytecode can be compact. Instructions communicate through the stack.

## 5.12 Calls

A function call is one of the most important runtime operations.

For:

```python
result = f(1, 2)
```

CPython must:

```text
evaluate f
evaluate arguments
prepare call layout
check callable type
create or enter callable execution
bind arguments
execute body or C function
return result
```

Different callable objects have different paths:

| Callable               | Runtime path                               |
| ---------------------- | ------------------------------------------ |
| Python function        | Create frame and execute code object       |
| Built-in function      | Call C function wrapper                    |
| Method                 | Bind receiver and call underlying function |
| Class                  | Allocate and initialize instance           |
| Object with `__call__` | Invoke type call slot                      |

CPython has optimized calling conventions to reduce temporary tuple and dictionary allocation. Modern CPython uses fast call paths such as vectorcall for many callable types.

## 5.13 Exceptions

Exceptions are runtime state plus control flow.

When Python code raises:

```python
raise ValueError("bad")
```

CPython records exception information in the current thread state and begins unwinding execution.

A C function usually reports failure by:

```text
setting an exception
returning NULL or -1
```

The caller checks the return value and propagates failure.

Conceptually:

```c
PyErr_SetString(PyExc_ValueError, "bad");
return NULL;
```

At bytecode level, exceptions affect:

```text
current frame
exception table
stack unwinding
finally blocks
except matching
traceback construction
propagation to caller
```

Exceptions are not ordinary return values. They are a separate control path through the runtime.

## 5.14 Tracebacks

A traceback records where an exception traveled.

When an exception propagates through frames, CPython can attach traceback entries that identify:

```text
file name
function name
line number
bytecode position
frame
```

For example:

```python
def a():
    b()

def b():
    1 / 0

a()
```

The traceback contains the call chain:

```text
module frame
a frame
b frame
```

Tracebacks are objects. Holding a traceback can keep frames alive. Holding frames can keep local variables alive. This is important for memory behavior.

## 5.15 Modules

A module is an object with a namespace.

When CPython imports a module, it creates or retrieves a module object and executes code in that module’s dictionary.

Conceptually:

```text
find module spec
create module object
insert into sys.modules
execute module code in module namespace
return module object
```

The module dictionary becomes the global namespace for functions defined in that module.

For:

```python
# example.py
x = 10

def f():
    return x
```

the function `f` stores a reference to the module globals dictionary. When `f` looks up `x`, it searches that dictionary.

## 5.16 `sys.modules`

`sys.modules` is the import cache.

It maps module names to module objects.

```python
import sys
print(sys.modules["sys"])
```

This cache prevents repeated imports from re-executing the same module.

Importing the same module twice usually returns the already loaded module:

```python
import math
import math
```

The second import checks `sys.modules` and reuses the module.

This cache also handles circular imports. A module may appear in `sys.modules` before its code has finished executing.

## 5.17 Builtins

Builtins are names available when local and global lookup fail.

Examples:

```python
len
print
range
object
type
Exception
```

A frame has access to a builtins dictionary.

Name lookup for an unqualified name inside a function roughly follows:

```text
locals
globals
builtins
```

So:

```python
def f(xs):
    return len(xs)
```

usually resolves `len` from builtins unless a global named `len` shadows it.

This lookup path is part of the runtime model. It explains why assigning a global named `len` changes behavior inside the module.

## 5.18 Descriptors and Attribute Access

Attribute access is runtime dispatch.

For:

```python
obj.name
```

CPython does not simply look inside `obj.__dict__`.

It follows descriptor and type lookup rules:

```text
look on type for data descriptor
look in instance dictionary
look on type for non-data descriptor or class attribute
call __getattr__ if needed
raise AttributeError if missing
```

This is why methods bind automatically:

```python
class C:
    def f(self):
        return 1

c = C()
m = c.f
```

The function object stored on the class is a descriptor. Accessing it through an instance creates a bound method or an optimized equivalent call path.

Attribute lookup connects object layout, type objects, descriptors, method calls, and performance.

## 5.19 Iteration

Iteration uses a small runtime protocol.

For:

```python
for x in obj:
    body(x)
```

CPython does roughly:

```text
iterator = iter(obj)
loop:
    x = next(iterator)
    if StopIteration: exit loop
    execute body
```

At C level, this maps to type slots and protocol helpers.

The iterator object stores iteration state. For a list iterator, that state includes the list and current index. For a generator, the iterator is the suspended execution frame itself.

This common protocol powers:

```text
for loops
comprehensions
tuple unpacking
list()
sum()
any()
all()
many standard library functions
```

## 5.20 Generators and Coroutines

A generator is a suspended frame.

For:

```python
def gen():
    yield 1
    yield 2
```

calling `gen()` does not immediately execute the body. It creates a generator object.

The generator owns execution state:

```text
code object
suspended frame or frame-like state
instruction position
local variables
exception state
running flag
```

Calling `next()` resumes execution until the next `yield` or return.

Coroutines and async generators extend this idea with awaitable protocol behavior and event-loop integration.

## 5.21 Memory Management

The runtime manages memory at several layers.

```text
raw memory allocator
object allocator
type-specific free lists or caches
reference counting
cyclic garbage collector
```

Reference counting handles most lifetime events:

```text
new reference increases lifetime
decref releases ownership
zero refcount triggers deallocation
```

The cyclic garbage collector handles unreachable object cycles.

Memory management is part of runtime behavior because object destruction can execute code through finalizers, weakref callbacks, or deallocation paths that release more objects.

## 5.22 The GIL in the Runtime Model

In the traditional CPython runtime, the Global Interpreter Lock protects execution of Python bytecode and many internal data structures.

The GIL simplifies:

```text
reference count updates
object mutation invariants
interpreter state access
C extension assumptions
```

A thread must hold the GIL to execute Python bytecode.

C extensions can release the GIL around blocking or long-running native work, then reacquire it before touching Python objects again.

The runtime model therefore has two layers of concurrency:

```text
OS threads may run concurrently
Python bytecode execution is serialized by the GIL in traditional builds
```

Modern CPython also has free-threaded build work, which changes many internal assumptions. But the traditional GIL model remains essential for understanding existing code and extensions.

## 5.23 Initialization

Before Python code runs, CPython initializes the runtime.

Startup includes:

```text
configure memory allocators
initialize runtime state
create main interpreter
create main thread state
initialize builtins
initialize sys
set up import machinery
initialize encodings
configure paths
process command-line options
run startup hooks
execute requested code
```

This is why startup code is complex. Many modules depend on other modules already existing, but the import system itself also needs runtime support.

CPython bootstraps itself carefully.

## 5.24 Shutdown

Shutdown is also complex.

Finalization may involve:

```text
running atexit handlers
flushing standard streams
clearing modules
destroying interpreter state
collecting garbage
finalizing objects
releasing memory
tearing down runtime services
```

Objects may run finalizers during shutdown, but their module globals may already be cleared. This is why shutdown bugs can be subtle.

A finalizer that expects `sys`, `os`, or another module to be fully available may fail during interpreter teardown.

## 5.25 Embedded Python

CPython can be embedded inside another C or C++ program.

In that case, the host process controls runtime initialization and finalization.

Conceptually:

```c
Py_Initialize();
/* run Python code */
Py_Finalize();
```

Embedding makes the runtime hierarchy more visible. The CPython runtime lives inside a process that may have its own threads, allocators, event loops, logging systems, and shutdown rules.

Embedding code must respect:

```text
initialization order
thread state management
GIL rules
reference ownership
exception handling
finalization constraints
```

## 5.26 Runtime State and Observability

Python exposes parts of the runtime through standard modules.

| Module        | Runtime view                                           |
| ------------- | ------------------------------------------------------ |
| `sys`         | Interpreter settings, modules, path, frames, refcounts |
| `gc`          | Garbage collector state                                |
| `inspect`     | Frames, functions, source, signatures                  |
| `dis`         | Bytecode                                               |
| `types`       | Runtime type objects                                   |
| `threading`   | Python thread abstractions                             |
| `tracemalloc` | Allocation traces                                      |
| `importlib`   | Import machinery                                       |

Examples:

```python
import sys
import gc
import inspect

print(sys.modules.keys())
print(gc.get_count())
print(inspect.currentframe())
```

These modules are useful because they let you observe runtime structures without immediately reading C code.

## 5.27 A Complete Execution Sketch

For this program:

```python
def add(a, b):
    return a + b

print(add(2, 3))
```

CPython roughly does:

```text
initialize runtime
create main interpreter
create main thread state
load builtins and sys
compile module source to code object
create module frame
execute module bytecode

define function:
    create code object for add
    create function object
    bind name add in module globals

call print(add(2, 3)):
    load print from builtins
    load add from globals
    load constants 2 and 3
    call add
        create frame for add
        bind a = 2, b = 3
        load a
        load b
        perform binary addition through object protocol
        return integer result
    call print
        execute built-in C function
        write output
    discard return value

finish module frame
run shutdown sequence
```

This is the runtime model in action.

## 5.28 Working Mental Model

Keep this compact model while reading later chapters:

```text
A process owns a CPython runtime.
The runtime owns interpreters.
An interpreter owns modules, builtins, import state, and GC state.
A thread state owns the current execution state for one thread.
A frame runs one code object.
Bytecode instructions operate on a value stack and local storage.
Objects carry type pointers.
Types define behavior through slots.
Errors use exception state plus sentinel returns.
Memory is managed by reference counting plus cyclic GC.
```

This model connects most CPython internals.

## 5.29 Chapter Summary

The CPython runtime is a layered execution system. The process contains a runtime. The runtime contains interpreter states. Each executing thread has a thread state. Each active call has a frame. Each frame runs a code object. Bytecode manipulates object references. Objects point to types. Types define behavior.

Understanding this hierarchy makes the rest of CPython easier to read. Startup, imports, function calls, exceptions, generators, garbage collection, and shutdown all fit into the same runtime model.

