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:
sys.modulesdict lookup (fast path).- Frozen
importlib._bootstrapbootstrap sequence. - A source-file loader (
.pysource to parse + compile + exec). - A .pyc bytecode-cache loader (read, validate via source hash or mtime, exec).
- The
IMPORT_NAME,IMPORT_FROM, andIMPORT_STARbytecodes in the VM. - 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
| Function | Location |
|---|---|
PyImport_ImportModuleLevelObject | Python/import.c:3460 |
PyImport_ImportFrozenModuleObject | Python/import.c:3095 |
init_importlib | Python/import.c:3185 |
PyImport_ExecCodeModuleObject | Python/import.c:2726 |
PyImport_GetModule | Python/import.c:149 |
PyImport_AddModuleObject | Python/import.c:160 |
PyImport_RemoveModule | Python/import.c:191 |
get_module_dict (sys.modules accessor) | Python/import.c:2549 |
PyImport_Inittab builtin table | Python/import.c global |
FrozenModule table | Python/frozen.c |
_find_frozen | Python/import.c:2836 |
IMPORT_NAME bytecode | Python/bytecodes.c IMPORT_NAME |
IMPORT_FROM bytecode | Python/bytecodes.c IMPORT_FROM |
IMPORT_STAR bytecode | Python/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 source | Go target |
|---|---|
Python/frozen.c frozen module table | imp/frozen.go |
| CPython 3.14 frozen bytecode blobs | imp/frozen_bootstrap.go (generated) |
Python/import.c:149 PyImport_GetModule | imp/sysmodules.go |
Python/import.c:160 PyImport_AddModuleObject | imp/sysmodules.go |
Python/import.c:191 PyImport_RemoveModule | imp/sysmodules.go |
Python/import.c:2549 get_module_dict | imp/sysmodules.go |
Python/import.c:3460 PyImport_ImportModuleLevelObject | imp/import.go |
Python/import.c:3095 PyImport_ImportFrozenModuleObject | imp/frozen.go |
Python/import.c:2726 PyImport_ExecCodeModuleObject | imp/exec.go |
Python/import.c:3185 init_importlib | imp/bootstrap.go |
PyImport_Inittab builtin module table | imp/inittab.go |
| .pyc cache read/write helpers | imp/loader.go |
Python/bytecodes.c IMPORT_NAME | vm/opcodes_import.go |
Python/bytecodes.c IMPORT_FROM | vm/opcodes_import.go |
Python/bytecodes.c IMPORT_STAR | vm/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),GetAttrrouting 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),ModuleNotFoundErroras a subclass ofImportError. CPython:Objects/exceptions.cImportError arm.
imp/frozen.go
-
FrozenModulestruct matchingstruct _frozen(Include/cpython/import.h). -
frozenModulesslice (the Go equivalent of the C_PyImport_FrozenModulesarray). Entries:_frozen_importlib,_frozen_importlib_external,_collections_abc,_sitebuiltins. CPython:Python/frozen.c. -
FindFrozen(name string): linear scan offrozenModules, returns(*FrozenModule, bool). CPython:Python/import.c:2836 _find_frozen. -
ExecFrozen(name string): callmarshal.ReadCodeFromBytes, thenExecCodeModule. CPython:Python/import.c:3095 PyImport_ImportFrozenModuleObject. - Frozen packages set
__path__to[name]on the module after exec. CPython:Python/import.c:3095package path fixup.
imp/frozen_bootstrap.go
-
frozenImportlibBootstrap []byte: copy theuint8array from CPython 3.14Python/frozen_modules/importlib._bootstrap.hverbatim as a Go byte literal. Do not attempt to compile from source. -
frozenImportlibExternalBootstrap []byte: same fromPython/frozen_modules/importlib._bootstrap_external.h. -
frozenCollectionsAbc []byte: same fromPython/frozen_modules/abc.h(which backs_collections_abc). -
frozenSitebuiltins []byte: same fromPython/frozen_modules/site.h(which backs_sitebuiltins). - Each array is referenced by the
FrozenModuleentries inimp/frozen.go.
imp/sysmodules.go
-
GetModule(name string): look upsys.modules[name], return nil if absent or if value isNone(the "blocked" sentinel). CPython:Python/import.c:149 PyImport_GetModule. -
AddModule(name string): ifsys.modules[name]already exists and is a module, return it; otherwise create a newobjects.Modulewith__name__ = name, insert it, return it. CPython:Python/import.c:160 PyImport_AddModuleObject. -
RemoveModule(name string): delete fromsys.modules. CPython:Python/import.c:191 PyImport_RemoveModule. -
modulesDict(): return the*objects.Dictforsys.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:- Create a minimal
_impbuiltin module (seeimp/inittab.go). - Call
ExecFrozen("_frozen_importlib"). - Retrieve
_frozen_importlib._install(sys, _imp)and call it. - Call
ExecFrozen("_frozen_importlib_external"). - Call
_frozen_importlib_external._install(_frozen_importlib).
- Create a minimal
- If bootstrap fails, return a wrapped
ImportErrorwith the module name and cause. CPython:Python/import.c:3185error path.
imp/inittab.go
-
Inittab: amap[string]func() (*objects.Module, error)that maps builtin module names to their initializer. CPython:Python/import.cPyImport_Inittabglobal array. - Initial entries:
_imp(the import helper functions that_frozen_importlibcalls back into Go for),_iostub. -
_impmodule 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_impmethod table. -
FindBuiltin(name string): checkInittaband return the initializer function if present.
imp/exec.go
-
ExecCodeModule: create a newobjects.ModuleviaAddModule, set__file__topathname, set__cached__tocpathname, 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.modulesbefore returning the error. CPython:Python/import.c:2726cleanup path. - Set
__spec__on the module after exec using a minimalModuleSpecstruct. CPython:Python/import.c:2726spec fixup.
imp/loader.go
-
LoadSource(name, path string): read.pyfile, parse + compile to a code object, optionally validate or write a.pyccache, then callExecCodeModule. - Source-file hash computation for the
.pyccache: SipHash-1-3 of the source bytes, matchingLib/importlib/_bootstrap_external.py _get_hash. -
.pyccache path:__pycache__/<stem>.cpython-314.pycnext to the source file. CPython:Lib/importlib/_bootstrap_external.py cache_from_source. - Read
.pycviamarshal.ReadPyc; on stale/missing cache, fall back to compiling from source and writing a fresh.pyc. - Write
.pycviamarshal.WritePycafter successful compile. -
LoadBytecode(name, cpathname string): load directly from a.pycfile without a.pycounterpart.
imp/import.go
-
ImportModuleLevel: CPython:Python/import.c:3460 PyImport_ImportModuleLevelObject.- Resolve relative
levelto an absolute name using the__package__or__spec__.parentofglobals. - Fast path: return immediately if
sys.modules[absname]exists and is notNone. - Try
FindFrozen; if found, callExecFrozen. - Try
FindBuiltin; if found, call the initializer. - Try
LoadSourceby searchingsys.path. - If nothing found, raise
ModuleNotFoundError.
- Resolve relative
-
resolveRelative(level int, globals *objects.Dict) string: stripslevelcomponents from__package__(or infers package from__name__). CPython:Python/import.c:3460relative resolution block. - On successful import return the top-level name if
fromlistis empty, or the final sub-module iffromlistis non-empty. CPython:Python/import.c:3460fromlist handling.
vm/opcodes_import.go
-
IMPORT_NAMEhandler: popfromlist(TOS) andlevel(TOS1) from the value stack, readco_names[namei], callimp.ImportModuleLevel, push result. CPython:Python/bytecodes.c IMPORT_NAME. -
IMPORT_FROMhandler: peek at TOS (the module), readco_names[namei], callobjects.GetAttr; onAttributeErrorsynthesise the "cannot import name 'X' from 'Y'" message before re-raising. CPython:Python/bytecodes.c IMPORT_FROM. -
IMPORT_STARhandler: pop module from stack, check for__all__; if present, iterate it andSTORE_NAMEeach attribute; if absent, iteratemodule.__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); verifysys.modules["_collections_abc"]is set after the call. -
imp/import_test.go: import a temp.pyfile written to at.TempDir; verify the module attribute set in that file is accessible. -
imp/import_test.go: secondGetModulecall for an already-imported module returns the same*objects.Modulepointer without re-executing. -
imp/import_test.go:level=1relative import resolves againstglobals["__package__"]. -
imp/import_test.go: importing an absent module raisesModuleNotFoundError. -
imp/loader_test.go:LoadSourceround-trip: write a.pyfile, callLoadSource, verify a.pycwas created; call again and verify the.pycis 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.WritePycused byimp/loader.go: 1690.objects.Module: 1688 (Objects/moduleobject.c).objects.Codeneeded byExecCodeModule: 1687.ImportError/ModuleNotFoundError: 1686.- VM eval loop that dispatches IMPORT_NAME etc.: 1620 hot path.
sys.path,sys.modules,sys.meta_pathwiring: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_hooksprotocol. v0.8 uses the direct Go loader chain; full protocol in v0.9. importlib.util,importlib.resources,importlib.metadata. Stdlib bridge, not part ofPython/import.c.