Lib/runpy.py
cpython 3.14 @ ab2d84fe1023/Lib/runpy.py
runpy.py implements the two public entry points run_module and run_path, along with a collection of private helpers that together make python -m <module> work. The core job is to find a module on sys.path, read its source, set up an execution namespace with the right dunder variables (__name__, __spec__, __loader__, __file__, __cached__, __package__), and then call exec() in that namespace. The module is intentionally kept small so it can be imported before most of the standard library is available.
Map
| Lines | Symbol | Role | gopy |
|---|---|---|---|
| 1-30 | imports, __all__ | importlib, io, types, os, sys | n/a |
| 31-70 | _run_code | Execute code object in a prepared namespace | Not yet ported |
| 71-110 | _run_module_code | Wrap _run_code with a temporary sys.modules entry | Not yet ported |
| 111-160 | _get_module_details | Resolve a dotted name to (pkg_name, spec, code) | Not yet ported |
| 161-220 | run_module | Public API; locate and run a named module | Not yet ported |
| 221-270 | _get_code_from_file | Read source or bytecode from a filesystem path | Not yet ported |
| 271-320 | run_path | Public API; run a script file or zip archive | Not yet ported |
Reading
_run_code: the execution primitive (lines 31 to 70)
cpython 3.14 @ ab2d84fe1023/Lib/runpy.py#L31-70
_run_code is the innermost function. It takes a code object, a mutable run_globals dict, and optional values for each dunder. It writes those values into the dict and calls exec(code, run_globals). The return value is the same run_globals dict after execution, so callers can inspect what the script defined.
def _run_code(code, run_globals, init_globals=None,
mod_name=None, mod_spec=None,
pkg_name=None, script_name=None):
if init_globals is not None:
run_globals.update(init_globals)
if mod_spec is None:
loader = None
fname = script_name
cached = None
else:
if mod_name is None:
mod_name = mod_spec.name
loader = mod_spec.loader
fname = mod_spec.origin
cached = mod_spec.cached
if pkg_name is None:
pkg_name = mod_spec.parent
run_globals.update(__name__ = mod_name,
__file__ = fname,
__cached__ = cached,
__doc__ = None,
__loader__ = loader,
__package__ = pkg_name,
__spec__ = mod_spec)
exec(code, run_globals)
return run_globals
Setting __spec__ and __loader__ correctly is what lets code inside the module call importlib.resources and similar APIs relative to its own package.
_get_module_details: resolving a dotted name (lines 111 to 160)
cpython 3.14 @ ab2d84fe1023/Lib/runpy.py#L111-160
Given a dotted name like http.server, this function uses importlib.util.find_spec to locate the module, then retrieves its code object via the loader. If the spec points to a package (i.e. the name resolves to a directory with an __init__), the function descends into __main__ inside that package. This is what makes python -m http.server work even though the runnable code lives in http/server.py, and also what makes python -m pytest work when pytest is a package with a __main__.py.
def _get_module_details(mod_name, error=ImportError):
if mod_name.startswith("."):
raise error("Relative module names not supported")
pkg_name, _, _ = mod_name.rpartition(".")
if pkg_name:
try:
__import__(pkg_name)
except ImportError as e:
raise error(format(e)) from e
try:
spec = importlib.util.find_spec(mod_name)
except (ImportError, ValueError) as e:
raise error(format(e)) from e
if spec is None:
raise error("No module named %s" % mod_name)
if spec.submodule_search_locations is not None:
# It is a package; try __main__ inside it
mod_name = mod_name + ".__main__"
return _get_module_details(mod_name, error)
...
return pkg_name, mod_spec, code
The recursive call to _get_module_details for the .__main__ case means packages can be arbitrarily deeply nested.
run_module: the public entry point (lines 161 to 220)
cpython 3.14 @ ab2d84fe1023/Lib/runpy.py#L161-220
run_module is the function that Modules/main.c reaches when the interpreter sees -m. It calls _get_module_details, builds an initial globals dict, and delegates to _run_module_code. The alter_sys flag, when true, replaces sys.argv[0] with the module's file path and temporarily sets sys.modules['__main__'] to the module being run, exactly mimicking what happens when the interpreter starts a script directly.
def run_module(mod_name, run_name=None, alter_sys=False,
init_globals=None):
mod_name, mod_spec, pkg_main_name, mod_main_name, code = (
_get_module_details(mod_name))
if run_name is None:
run_name = mod_name
if alter_sys:
sys.argv[0] = mod_spec.origin
return _run_module_code(code,
init_globals,
run_name,
mod_spec=mod_spec,
alter_sys=alter_sys,
pkg_name=pkg_main_name)
The separation between run_module (public, stable ABI) and _run_module_code (private) allows the interpreter startup code to call the lower-level helper directly without going through argument defaulting.
gopy mirror
Not yet ported. When gopy gains a -m flag equivalent, runpy will need to be implemented. The most natural Go translation is a package-level function RunModule(modName string, alterSys bool) (map[string]Object, error) that calls the import machinery to fetch a module's code object and then hands it to the VM's Exec entry point with a freshly constructed globals dict. The dunder-setting logic in _run_code maps directly to populating a vm.Frame before dispatch.
CPython 3.14 changes
CPython 3.14 updated runpy to thread-safely handle sys.modules manipulation under the free-threaded build. The _run_module_code function now uses a per-interpreter lock when temporarily inserting the __main__ entry into sys.modules, preventing races when multiple threads call run_module concurrently. The public API signatures are unchanged.