Skip to main content

1691. gopy import

What we are porting

Python/import.c (4956 lines) and Python/frozen.c (132 lines): the full module import machinery.

The import system is the gating deliverable for v0.8. import json; json.dumps({"a": 1}) requires all of the following to work:

  1. sys.modules dict lookup (fast path).
  2. Frozen importlib._bootstrap bootstrap sequence.
  3. A source-file loader (.py source to parse + compile + exec).
  4. A .pyc bytecode-cache loader (read, validate via source hash or mtime, exec).
  5. The IMPORT_NAME, IMPORT_FROM, and IMPORT_STAR bytecodes in the VM.
  6. A builtin module table analogous to PyImport_Inittab.

CPython's import path flows through PyImport_ImportModuleLevelObject (Python/import.c:3460), which delegates to importlib._bootstrap._find_and_load, which walks sys.meta_path finders. For v0.8, gopy bypasses sys.meta_path and implements the two loaders directly in Go. Full meta_path protocol comes later.

The frozen bootstrap blobs (_frozen_importlib, _frozen_importlib_external, _collections_abc, _sitebuiltins) are taken verbatim from CPython 3.14's Programs/_freeze_module-generated C headers, converted to Go byte literals. gopy re-uses the pre-compiled bytecode rather than re-compiling with its own compiler, because the bytecode format is identical.

Key CPython functions

FunctionLocation
PyImport_ImportModuleLevelObjectPython/import.c:3460
PyImport_ImportFrozenModuleObjectPython/import.c:3095
init_importlibPython/import.c:3185
PyImport_ExecCodeModuleObjectPython/import.c:2726
PyImport_GetModulePython/import.c:149
PyImport_AddModuleObjectPython/import.c:160
PyImport_RemoveModulePython/import.c:191
get_module_dict (sys.modules accessor)Python/import.c:2549
PyImport_Inittab builtin tablePython/import.c global
FrozenModule tablePython/frozen.c
_find_frozenPython/import.c:2836
IMPORT_NAME bytecodePython/bytecodes.c IMPORT_NAME
IMPORT_FROM bytecodePython/bytecodes.c IMPORT_FROM
IMPORT_STAR bytecodePython/bytecodes.c IMPORT_STAR

IMPORT_NAME

Python/bytecodes.c IMPORT_NAME: pops fromlist and level from the stack, looks up co_names[namei], calls PyImport_ImportModuleLevelObject(name, globals, locals, fromlist, level), pushes the returned module.

IMPORT_FROM

Python/bytecodes.c IMPORT_FROM: reads co_names[namei], calls PyObject_GetAttr(module, name); on AttributeError with a package, synthesises a more helpful error message before re-raising.

IMPORT_STAR

Python/bytecodes.c IMPORT_STAR: if the module defines __all__, iterates it and copies each attribute into the local namespace. If __all__ is absent, copies every attribute whose name does not start with _.

Go shape

// FrozenModule mirrors struct _frozen from Include/cpython/import.h.
type FrozenModule struct {
Name string
Code []byte // marshalled code object bytes
IsPackage bool
}

// FindFrozen looks up name in the frozen module table.
// CPython: Python/import.c:2836 _find_frozen
func FindFrozen(name string) (*FrozenModule, bool)

// ExecFrozen imports a frozen module by name.
// Unmarshals Code, runs it in a new module, registers in sys.modules.
// CPython: Python/import.c:3095 PyImport_ImportFrozenModuleObject
func ExecFrozen(name string) (*objects.Module, error)

// ImportModuleLevel is the entry point for IMPORT_NAME.
// Handles relative-to-absolute resolution then delegates to the loader chain.
// CPython: Python/import.c:3460 PyImport_ImportModuleLevelObject
func ImportModuleLevel(name string, globals, locals *objects.Dict, fromlist *objects.Tuple, level int) (*objects.Module, error)

// AddModule returns or creates the sys.modules entry for name.
// CPython: Python/import.c:160 PyImport_AddModuleObject
func AddModule(name string) (*objects.Module, error)

// GetModule returns the module from sys.modules, or nil if absent.
// CPython: Python/import.c:149 PyImport_GetModule
func GetModule(name string) (*objects.Module, error)

// RemoveModule deletes name from sys.modules.
// CPython: Python/import.c:191 PyImport_RemoveModule
func RemoveModule(name string) error

// ExecCodeModule executes a code object in a new module and registers it.
// CPython: Python/import.c:2726 PyImport_ExecCodeModuleObject
func ExecCodeModule(name string, co *objects.Code, pathname, cpathname string) (*objects.Module, error)

File mapping

C sourceGo target
Python/frozen.c frozen module tableimp/frozen.go
CPython 3.14 frozen bytecode blobsimp/frozen_bootstrap.go (generated)
Python/import.c:149 PyImport_GetModuleimp/sysmodules.go
Python/import.c:160 PyImport_AddModuleObjectimp/sysmodules.go
Python/import.c:191 PyImport_RemoveModuleimp/sysmodules.go
Python/import.c:2549 get_module_dictimp/sysmodules.go
Python/import.c:3460 PyImport_ImportModuleLevelObjectimp/import.go
Python/import.c:3095 PyImport_ImportFrozenModuleObjectimp/frozen.go
Python/import.c:2726 PyImport_ExecCodeModuleObjectimp/exec.go
Python/import.c:3185 init_importlibimp/bootstrap.go
PyImport_Inittab builtin module tableimp/inittab.go
.pyc cache read/write helpersimp/loader.go
Python/bytecodes.c IMPORT_NAMEvm/opcodes_import.go
Python/bytecodes.c IMPORT_FROMvm/opcodes_import.go
Python/bytecodes.c IMPORT_STARvm/opcodes_import.go

Checklist

Status legend: [x] shipped, [ ] pending, [~] partial / scaffold, [n] deferred / not in scope this phase.

Prerequisites (must land before this spec)

  • objects/module.go: Module struct, NewModule(name string), GetAttr routing through __dict__ with PEP 562 __getattr__ fallback, SetAttr, __dir__, repr (<module 'name' from 'path'>). CPython: Objects/moduleobject.c.
  • objects/exc_import.go: ImportError(msg, name, path), ModuleNotFoundError as a subclass of ImportError. CPython: Objects/exceptions.c ImportError arm.

imp/frozen.go

  • FrozenModule struct matching struct _frozen (Include/cpython/import.h).
  • frozenModules slice (the Go equivalent of the C _PyImport_FrozenModules array). Entries: _frozen_importlib, _frozen_importlib_external, _collections_abc, _sitebuiltins. CPython: Python/frozen.c.
  • FindFrozen(name string): linear scan of frozenModules, returns (*FrozenModule, bool). CPython: Python/import.c:2836 _find_frozen.
  • ExecFrozen(name string): call marshal.ReadCodeFromBytes, then ExecCodeModule. CPython: Python/import.c:3095 PyImport_ImportFrozenModuleObject.
  • Frozen packages set __path__ to [name] on the module after exec. CPython: Python/import.c:3095 package path fixup.

imp/frozen_bootstrap.go

  • frozenImportlibBootstrap []byte: copy the uint8 array from CPython 3.14 Python/frozen_modules/importlib._bootstrap.h verbatim as a Go byte literal. Do not attempt to compile from source.
  • frozenImportlibExternalBootstrap []byte: same from Python/frozen_modules/importlib._bootstrap_external.h.
  • frozenCollectionsAbc []byte: same from Python/frozen_modules/abc.h (which backs _collections_abc).
  • frozenSitebuiltins []byte: same from Python/frozen_modules/site.h (which backs _sitebuiltins).
  • Each array is referenced by the FrozenModule entries in imp/frozen.go.

imp/sysmodules.go

  • GetModule(name string): look up sys.modules[name], return nil if absent or if value is None (the "blocked" sentinel). CPython: Python/import.c:149 PyImport_GetModule.
  • AddModule(name string): if sys.modules[name] already exists and is a module, return it; otherwise create a new objects.Module with __name__ = name, insert it, return it. CPython: Python/import.c:160 PyImport_AddModuleObject.
  • RemoveModule(name string): delete from sys.modules. CPython: Python/import.c:191 PyImport_RemoveModule.
  • modulesDict(): return the *objects.Dict for sys.modules. CPython: Python/import.c:2549 get_module_dict.

imp/bootstrap.go

  • InitImportlib(): the bootstrap sequence run once at interpreter startup. CPython: Python/import.c:3185 init_importlib. Steps:
    1. Create a minimal _imp builtin module (see imp/inittab.go).
    2. Call ExecFrozen("_frozen_importlib").
    3. Retrieve _frozen_importlib._install(sys, _imp) and call it.
    4. Call ExecFrozen("_frozen_importlib_external").
    5. Call _frozen_importlib_external._install(_frozen_importlib).
  • If bootstrap fails, return a wrapped ImportError with the module name and cause. CPython: Python/import.c:3185 error path.

imp/inittab.go

  • Inittab: a map[string]func() (*objects.Module, error) that maps builtin module names to their initializer. CPython: Python/import.c PyImport_Inittab global array.
  • Initial entries: _imp (the import helper functions that _frozen_importlib calls back into Go for), _io stub.
  • _imp module must expose at minimum: is_frozen(name), is_builtin(name), create_builtin(spec), exec_builtin(mod), is_frozen_package(name), get_frozen_object(name), create_dynamic, exec_dynamic. CPython: Python/import.c _imp method table.
  • FindBuiltin(name string): check Inittab and return the initializer function if present.

imp/exec.go

  • ExecCodeModule: create a new objects.Module via AddModule, set __file__ to pathname, set __cached__ to cpathname, set __loader__ to the loader used, eval the code object in the module's __dict__, return the module. CPython: Python/import.c:2726 PyImport_ExecCodeModuleObject.
  • On eval error, remove the module from sys.modules before returning the error. CPython: Python/import.c:2726 cleanup path.
  • Set __spec__ on the module after exec using a minimal ModuleSpec struct. CPython: Python/import.c:2726 spec fixup.

imp/loader.go

  • LoadSource(name, path string): read .py file, parse + compile to a code object, optionally validate or write a .pyc cache, then call ExecCodeModule.
  • Source-file hash computation for the .pyc cache: SipHash-1-3 of the source bytes, matching Lib/importlib/_bootstrap_external.py _get_hash.
  • .pyc cache path: __pycache__/<stem>.cpython-314.pyc next to the source file. CPython: Lib/importlib/_bootstrap_external.py cache_from_source.
  • Read .pyc via marshal.ReadPyc; on stale/missing cache, fall back to compiling from source and writing a fresh .pyc.
  • Write .pyc via marshal.WritePyc after successful compile.
  • LoadBytecode(name, cpathname string): load directly from a .pyc file without a .py counterpart.

imp/import.go

  • ImportModuleLevel: CPython: Python/import.c:3460 PyImport_ImportModuleLevelObject.
    1. Resolve relative level to an absolute name using the __package__ or __spec__.parent of globals.
    2. Fast path: return immediately if sys.modules[absname] exists and is not None.
    3. Try FindFrozen; if found, call ExecFrozen.
    4. Try FindBuiltin; if found, call the initializer.
    5. Try LoadSource by searching sys.path.
    6. If nothing found, raise ModuleNotFoundError.
  • resolveRelative(level int, globals *objects.Dict) string: strips level components from __package__ (or infers package from __name__). CPython: Python/import.c:3460 relative resolution block.
  • On successful import return the top-level name if fromlist is empty, or the final sub-module if fromlist is non-empty. CPython: Python/import.c:3460 fromlist handling.

vm/opcodes_import.go

  • IMPORT_NAME handler: pop fromlist (TOS) and level (TOS1) from the value stack, read co_names[namei], call imp.ImportModuleLevel, push result. CPython: Python/bytecodes.c IMPORT_NAME.
  • IMPORT_FROM handler: peek at TOS (the module), read co_names[namei], call objects.GetAttr; on AttributeError synthesise the "cannot import name 'X' from 'Y'" message before re-raising. CPython: Python/bytecodes.c IMPORT_FROM.
  • IMPORT_STAR handler: pop module from stack, check for __all__; if present, iterate it and STORE_NAME each attribute; if absent, iterate module.__dict__ and store each key not starting with _. CPython: Python/bytecodes.c IMPORT_STAR.

Tests

  • imp/import_test.go: import a frozen module (_collections_abc); verify sys.modules["_collections_abc"] is set after the call.
  • imp/import_test.go: import a temp .py file written to a t.TempDir; verify the module attribute set in that file is accessible.
  • imp/import_test.go: second GetModule call for an already-imported module returns the same *objects.Module pointer without re-executing.
  • imp/import_test.go: level=1 relative import resolves against globals["__package__"].
  • imp/import_test.go: importing an absent module raises ModuleNotFoundError.
  • imp/loader_test.go: LoadSource round-trip: write a .py file, call LoadSource, verify a .pyc was created; call again and verify the .pyc is used (stub the compile step to detect re-use).
  • imp/loader_test.go: stale .pyc (wrong source hash) causes re-compile.
  • Gate test: gopy -c "import json; print(json.dumps({'a': 1}))" exits 0 and prints {"a": 1}.

Cross-references

  • marshal.ReadPyc / marshal.WritePyc used by imp/loader.go: 1690.
  • objects.Module: 1688 (Objects/moduleobject.c).
  • objects.Code needed by ExecCodeModule: 1687.
  • ImportError / ModuleNotFoundError: 1686.
  • VM eval loop that dispatches IMPORT_NAME etc.: 1620 hot path.
  • sys.path, sys.modules, sys.meta_path wiring: sys/ package (spec 1630 area).

Out of scope

  • importdl.c: C-extension (.so / .pyd) loading. Not applicable since gopy does not link C extensions.
  • Multi-phase module init (PEP 489). Deferred past v0.8.
  • Namespace packages (PEP 420 / implicit namespaces). Deferred to v0.9.
  • zipimport: zip-file importer. Deferred.
  • Import locking (_PyImport_AcquireLock). gopy is single-threaded through v0.10; the lock is a no-op stub.
  • Full sys.meta_path / sys.path_hooks protocol. v0.8 uses the direct Go loader chain; full protocol in v0.9.
  • importlib.util, importlib.resources, importlib.metadata. Stdlib bridge, not part of Python/import.c.