Skip to main content

Python/frozen.c

cpython 3.14 @ ab2d84fe1023/Python/frozen.c

The frozen module table. During interpreter startup, before sys.path exists and before importlib has been imported, the interpreter needs to import _frozen_importlib (the pure-Python importlib._bootstrap module) and _frozen_importlib_external (importlib._bootstrap_external). Those modules cannot be loaded from disk because the path-based importer does not exist yet. The solution is to compile them to bytecode at CPython build time, embed the marshal'd code objects as C byte arrays, and register them in this file's tables.

Python/frozen.c contains three tables: _PyImport_FrozenBootstrap for the two modules needed in the very first import step, _PyImport_FrozenStdlib for a larger set of stdlib modules that are frozen for startup performance (PEP 451 bootstrap, zipimport, _collections_abc, _abc, etc.), and _PyImport_FrozenAliases which maps alternate module names (e.g. __hello__) to entries in the other two tables.

Each table is a null-terminated array of struct _frozen entries with three fields: name (C string), code (pointer to a byte array), and size (byte count of the marshal'd code object, negative for a package).

PyImport_ImportFrozenModule in Python/import.c drives the lookup: it walks both bootstrap and stdlib tables, then aliases, to find a match by name; unmarshals the code object via PyMarshal_ReadObjectFromString; and executes it in a freshly created module namespace.

Map

LinesSymbolRolegopy
1-30file header / includesIncludes Python.h and the generated frozen_data headers produced by Tools/freeze/.stdlibinit/registry.go
31-80_PyImport_FrozenBootstrapTwo-entry table: _frozen_importlib and _frozen_importlib_external. These are the only modules available during phase-one init.stdlibinit/registry.go:FrozenBootstrap
81-140_PyImport_FrozenStdlibLarger table of stdlib modules frozen for performance (e.g. zipimport, abc, _collections_abc, _sitebuiltins, genericpath).stdlibinit/registry.go:FrozenStdlib
141-175_PyImport_FrozenAliasesAlias table: maps alternate names (__hello__, __phello__, __phello__._framework) to entries in the other tables.stdlibinit/registry.go:FrozenAliases
176-200terminator / PyImport_FrozenModulesThe public PyImport_FrozenModules pointer used by the C extension API; defaults to _PyImport_FrozenBootstrap. Embedding applications may redirect it to a custom table.stdlibinit/registry.go:FrozenModules

Reading

Frozen table layout (lines 31 to 140)

cpython 3.14 @ ab2d84fe1023/Python/frozen.c#L31-140

/* Bootstrap frozen modules */
static const struct _frozen _PyImport_FrozenBootstrap[] = {
{"_frozen_importlib", _Py_M__importlib__bootstrap,
(int)sizeof(_Py_M__importlib__bootstrap)},
{"_frozen_importlib_external", _Py_M__importlib__bootstrap_external,
(int)sizeof(_Py_M__importlib__bootstrap_external)},
{0, 0, 0} /* sentinel */
};

The struct _frozen layout is:

struct _frozen {
const char *name;
const unsigned char *code;
int size;
int is_package; /* added 3.11 */
PyObject *(*get_code)(void); /* added 3.13, lazy-load hook */
};

size is positive for modules and negative for packages (the absolute value is the byte count). In 3.11 the is_package flag was added explicitly so the sign convention is no longer the only indicator. In 3.13 a get_code hook was added to allow lazy unmarshalling; when non-NULL it is called instead of treating code as a pre-marshalled buffer.

The byte arrays (e.g. _Py_M__importlib__bootstrap) are generated by Tools/freeze/freeze.py and written to Python/frozen_modules/ at build time. They are never shipped as .pyc files; they are always linked directly into the interpreter binary.

PyImport_ImportFrozenModule (lines in Python/import.c)

cpython 3.14 @ ab2d84fe1023/Python/import.c#L3501-3700

int
PyImport_ImportFrozenModuleObject(PyObject *name)
{
/* 1. Search bootstrap table, then stdlib, then aliases */
const struct _frozen *p = _PyImport_FindFrozen(name);
if (p == NULL) return 0; /* not found */

/* 2. Unmarshal the code object */
PyObject *co = PyMarshal_ReadObjectFromString(
(const char *)p->code, p->size);
if (co == NULL) return -1;

/* 3. Execute in a new module namespace */
PyObject *m = PyImport_AddModuleObject(name);
...
PyObject *d = PyModule_GetDict(m);
...
PyObject *v = PyEval_EvalCode(co, d, d);
Py_DECREF(co);
if (v == NULL) return -1;
Py_DECREF(v);
return 1; /* found and executed */
}

_PyImport_FindFrozen walks _PyImport_FrozenBootstrap, then _PyImport_FrozenStdlib, then resolves aliases from _PyImport_FrozenAliases before returning NULL. The unmarshalling is a direct PyMarshal_ReadObjectFromString call, which is safe at this stage because the marshal module is implemented entirely in C.

The returned integer follows the "found" protocol: 0 means not a frozen module (try the next finder), 1 means found and executed successfully, -1 means found but execution raised an exception.

Bootstrap sequence

The order in which frozen modules are needed during startup is:

  1. _PyImport_FrozenBootstrap[0] (_frozen_importlib) is executed in _PyImport_InitCore before any other import. It installs the bootstrap finder and loader into sys.meta_path.
  2. _PyImport_FrozenBootstrap[1] (_frozen_importlib_external) is executed next, adding the path-based finder to sys.meta_path. After this point, .py and .pyc files on sys.path can be imported.
  3. The stdlib frozen modules are available via the normal frozen finder from this point on; they are imported lazily on first use, not eagerly at startup.

In gopy, stdlibinit/registry.go stores the equivalent tables as Go slices of a FrozenEntry struct and drives the same three-step sequence inside InitCore.

CPython 3.14 changes worth noting

The get_code hook field was added to struct _frozen in 3.13 to support deferred unmarshalling for the larger stdlib frozen modules (gh-100950). _PyImport_FrozenStdlib grew significantly in 3.11 (PEP 451 performance work) and again in 3.12. The is_package flag made the negative-size convention for packages redundant; both are still supported for backward-compatibility with embedding code that hand-builds frozen tables.