Skip to main content

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

LinesSymbolRolegopy
1-30imports, __all__importlib, io, types, os, sysn/a
31-70_run_codeExecute code object in a prepared namespaceNot yet ported
71-110_run_module_codeWrap _run_code with a temporary sys.modules entryNot yet ported
111-160_get_module_detailsResolve a dotted name to (pkg_name, spec, code)Not yet ported
161-220run_modulePublic API; locate and run a named moduleNot yet ported
221-270_get_code_from_fileRead source or bytecode from a filesystem pathNot yet ported
271-320run_pathPublic API; run a script file or zip archiveNot 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.