Skip to main content

Python/import.c (part 8)

Source:

cpython 3.14 @ ab2d84fe1023/Python/import.c

This annotation covers the import machinery internals. See python_import7_detail for __import__, importlib, module spec resolution, and sys.modules caching.

Map

LinesSymbolRole
1-80Import lockPer-module lock to prevent import races
81-160_PyImport_LoadDynamicModuleWithSpecLoad a .so / .pyd extension module
161-260Frozen modulesCompile bytecode embedded in the interpreter
261-380Namespace packages__path__ without __init__.py
381-500PyImport_ImportModuleLevelObjectLevel-relative import resolution

Reading

Import lock

// CPython: Python/import.c:220 _PyImport_AcquireLock
void
_PyImport_AcquireLock(PyInterpreterState *interp)
{
/* Per-module locking: two threads importing the same module
get serialized; different modules import concurrently. */
struct _import_lock *lock = &interp->imports.lock;
PyThread_acquire_lock(lock->mutex, WAIT_LOCK);
while (lock->locked && lock->thread != PyThread_get_thread_ident()) {
lock->waiters++;
PyThread_release_lock(lock->mutex);
PyThread_acquire_lock(lock->mutex, WAIT_LOCK);
lock->waiters--;
}
lock->locked = 1;
lock->thread = PyThread_get_thread_ident();
lock->level++;
PyThread_release_lock(lock->mutex);
}

The import lock is reentrant (same thread can acquire multiple times). Without it, two threads importing a module that has side effects could execute the module body concurrently.

Frozen modules

// CPython: Python/import.c:480 import_find_frozen
static PyObject *
import_find_frozen(PyThreadState *tstate, PyObject *name)
{
/* Search the table of frozen modules (compiled into the interpreter) */
struct _frozen *p;
for (p = PyImport_FrozenModules; p->name != NULL; p++) {
if (strcmp(p->name, PyUnicode_AsUTF8(name)) == 0) {
return unmarshal_frozen_module(tstate, p);
}
}
return Py_NewRef(Py_None); /* not found */
}

Frozen modules are bytecode bytes embedded in Python/frozen.c (generated by Tools/scripts/freeze.py). They bootstrap the import system before the filesystem is available. _frozen_importlib and _frozen_importlib_external are always frozen.

Namespace packages

// CPython: Python/import.c:640 _find_spec_legacy
static PyObject *
_find_spec_legacy(PyObject *finder, PyObject *name, PyObject *path)
{
/* Finder returned a loader; if None, it's a namespace package portion */
...
if (loader == Py_None) {
/* Namespace package: accumulate path portions */
portions = PyObject_GetAttr(loader, str_submodule_search_locations);
...
}
}

Namespace packages (PEP 420) have no __init__.py. Their __path__ is a _NamespacePath that searches multiple directories. Multiple installed packages can contribute to the same namespace package.

gopy notes

The import lock is vm.ImportLock in vm/eval_import.go, implemented with a sync.Mutex and a reentrancy counter per goroutine (using goroutine_id). Frozen modules are registered in stdlibinit/registry.go via registerFrozen. Namespace package path joining is module/importlib.NamespacePath.