Skip to main content

Include/internal/pycore_import.h

This page annotates

cpython 3.14 @ ab2d84fe1023/Include/internal/pycore_import.h

, the internal header for CPython's import subsystem. It declares the frozen-module table layout, the import lock, the bootstrap importer entry point, and the extension module object cache. None of these symbols are part of the stable ABI.

Map

LinesSymbolKindNotes
1-18guard macros, includespreprocessorpulls in pycore_interp.h, pystate.h
20-38struct _frozenstructone row per frozen module
40-55PyImport_FrozenModulesextern declNULL-terminated table of _frozen
57-72struct _import_lockstructper-interpreter recursive import lock
74-88_PyImport_AcquireLock / _PyImport_ReleaseLockfunction declsimport-lock lifecycle
90-108_PyImport_BootstrapImpfunction declinstalls _imp before site.py runs
110-128_PyImport_FindExtensionObjectfunction declextension module cache lookup
130-145_PyImport_GetModuleAttrStringfunction declconvenience attr fetch
147-160misc internal helpersfunction decls_PyImport_CheckSubinterpIncompatibleExtensionAllowed, etc.

Reading

struct _frozen and the frozen module table

CPython embeds a subset of the standard library as pre-compiled bytecode so that the import system can bootstrap before the filesystem is accessible. Each entry in the table describes one module.

// CPython: Include/internal/pycore_import.h:20 struct _frozen
struct _frozen {
const char *name; /* dotted module name, e.g. "importlib._bootstrap" */
const unsigned char *code; /* marshalled code object bytes */
int size; /* byte count; negative means a package */
int is_package; /* non-zero if the module is a package */
/* cpython 3.12+ adds get_code function pointer for lazy loading */
};

The global table is terminated by an entry with name == NULL:

// CPython: Include/internal/pycore_import.h:40 PyImport_FrozenModules
extern const struct _frozen *PyImport_FrozenModules;

At startup Py_InitializeFromConfig points this pointer at the table generated by Tools/scripts/freeze_modules.py. Third-party embedders may swap it to inject their own frozen modules before calling Py_Initialize.

_PyImport_BootstrapImp: installing the _imp builtin

The bootstrap sequence must install _imp (the C-level import module) before any Python-level import machinery is available. _PyImport_BootstrapImp performs that installation without going through the normal import path.

// CPython: Include/internal/pycore_import.h:90 _PyImport_BootstrapImp
PyObject *_PyImport_BootstrapImp(PyThreadState *tstate);

Internally the function calls _PyImport_FindBuiltin to locate _imp in the built-in module table, then stores the result into sys.modules["_imp"] so that importlib._bootstrap can reach it via a normal attribute lookup. This runs before importlib itself is imported, so no Python-level import lock is held.

The import lock: struct _import_lock and its API

CPython's import lock is a per-interpreter recursive mutex. It prevents two threads from running the same module's top-level code concurrently. The struct sits inside PyInterpreterState.

// CPython: Include/internal/pycore_import.h:57 struct _import_lock
struct _import_lock {
PyThread_type_lock lock; /* underlying OS lock */
unsigned long thread_id; /* owner thread, or PYTHREAD_INVALID_THREAD_ID */
int level; /* recursion depth */
};
// CPython: Include/internal/pycore_import.h:74 _PyImport_AcquireLock
int _PyImport_AcquireLock(PyInterpreterState *interp);
void _PyImport_ReleaseLock(PyInterpreterState *interp);

_PyImport_AcquireLock is recursive: if the calling thread already owns the lock the level counter is incremented and the call returns immediately. This is necessary because a module's top-level code may itself trigger further imports. The return value is 1 on success, 0 if the current thread was interrupted while waiting, and -1 if the lock is uninitialized.

_PyImport_FindExtensionObject: the extension cache

CPython caches initialized extension modules in a per-interpreter dict keyed by (filename, name) tuples. This avoids re-executing the module init function when the same .so is imported a second time.

// CPython: Include/internal/pycore_import.h:110 _PyImport_FindExtensionObject
PyObject *_PyImport_FindExtensionObject(PyObject *name, PyObject *filename);

The cache lives in interp->imports.extensions. A hit returns a borrowed reference to the cached module object. A miss returns NULL without setting an exception, so callers must use PyErr_Occurred() to distinguish a cache miss from an error.

gopy notes

Status: not yet ported.

Planned package path: module/importlib/ for the Python-level bootstrap logic, vm/ for the import lock (embedded on the interpreter struct alongside the GIL). The frozen module table will be represented as []frozenEntry in a generated stdlib/frozen_table.go file produced by the build tool that pre-compiles Lib/importlib/_bootstrap.py. _PyImport_FindExtensionObject maps to a sync.Map keyed by (filename, name) string pairs, held on vm.Interpreter.