Python runpy: Execute Modules and Scripts

Published on: August 27, 2026
Reading time: 6 minutes
Close-up view of a computer screen displaying code in a software development environment.

The runpy module executes Python code located through the import system or a filesystem path and returns the resulting global namespace. It implements part of the behavior behind commands such as python -m package.module and is useful in launchers, test harnesses, educational tools, CLI wrappers, and systems that need to run a module as a program.

The module does not provide isolation. Code runs in the current process and can modify global state, import dependencies, open files, start threads, or terminate the program. Use it only with trusted code and understand how it handles special variables such as __name__, __spec__, __package__, and selected sys values.

run_module

runpy.run_module(mod_name, ...) locates a module through Python’s import system and executes its code.

import runpy

namespace = runpy.run_module("my_package.module")
print(namespace.keys())

The module name must be importable in the current environment.

The return value is a dictionary

After execution, the result contains globals defined by the module.

result = runpy.run_module("settings")
value = result.get("CONFIG")

Returned objects remain alive and may reference modules, files, locks, and other resources.

run_name

run_name controls __name__ during execution.

runpy.run_module(
    "my_package.cli",
    run_name="__main__",
)

This activates blocks guarded by if __name__ == "__main__":.

Execute as __main__

Using run_name="__main__" approximates python -m, but exact behavior for sys.argv and sys.modules also depends on other options.

For a public CLI, launching a subprocess with -m is often simpler and more faithful.

Packages and __main__.py

When the name refers to a package, Python can locate and execute package.__main__.

my_package/
    __init__.py
    __main__.py

This is the conventional structure for python -m my_package.

alter_sys

With alter_sys=True, runpy temporarily changes selected sys state, including sys.argv[0] and an entry in sys.modules.

runpy.run_module(
    "my_package.cli",
    run_name="__main__",
    alter_sys=True,
)

The values are restored after completion, including when an exception occurs.

alter_sys is not thread-safe

Other threads can observe temporary values and may see a partially initialized module or unexpected argv.

Avoid alter_sys=True when concurrent threads depend on global interpreter state. Prefer a separate process.

init_globals

init_globals supplies initial values for the execution namespace.

namespace = runpy.run_module(
    "report",
    init_globals={"ENVIRONMENT": "test"},
)

The executed module can overwrite these keys.

init_globals is not security

A smaller dictionary does not prevent imports, filesystem access, builtins, or introspection.

Use it for configuration and controlled tests, not sandboxing.

Special global variables

Runpy initializes values such as __name__, __file__, __cached__, __loader__, __package__, and __spec__.

These values let relative imports and diagnostics behave similarly to normal module execution.

__spec__ identity

__spec__ describes how the module was found and loaded. In executable packages, its real name remains tied to the discovered module even when __name__ changes.

Use __spec__.name for import identity and __name__ for execution context.

run_path

runpy.run_path(path_name, ...) executes code from a path.

namespace = runpy.run_path("scripts/task.py")

The path can be a Python file or a valid sys.path entry containing __main__.py.

Executable directories

If the path is a directory, runpy temporarily makes it available and searches for __main__.py.

Verify that the directory contains the intended entry point so another visible __main__ cannot create surprising behavior.

ZIP files

A ZIP supported by the import system can contain __main__.py and be executed with run_path().

This underlies .pyz applications. See Python zipapp.

run_path and run_name

The default run name for run_path() is a special value. Set __main__ when main-program behavior is required.

runpy.run_path(
    "scripts/task.py",
    run_name="__main__",
)

Difference from importlib.import_module

importlib.import_module() performs a normal import and registers the module in sys.modules. Later imports usually reuse the same instance.

run_module() executes code in a fresh namespace for script-like behavior rather than ordinary library loading.

Repeated execution

Calling runpy twice can execute module-level effects twice.

Registrations, threads, handlers, file writes, and connections may be duplicated. Use code designed for repeated lifecycle or run it in a disposable process.

sys.modules effects

Depending on alter_sys, the executed module may not remain registered as a normal import.

Imports performed by the executed code do remain in sys.modules and change the host process.

Global state remains changed

The returned dictionary does not capture every effect. Code can change other modules, logging, locale, signals, working directory, and caches.

Discarding the namespace does not undo execution.

Exceptions propagate

Exceptions from the executed code are raised to the caller.

try:
    runpy.run_module("my_package.cli", run_name="__main__")
except SystemExit as error:
    print("exit code", error.code)

CLIs frequently call sys.exit(), which raises SystemExit.

KeyboardInterrupt

An interrupt can cross the execution boundary. Decide whether the launcher should stop, cancel only the task, or translate the status.

Do not catch BaseException without an explicit lifecycle policy.

Command-line arguments

Runpy is not a complete subprocess API. Simulating arguments by changing sys.argv affects the whole process.

Prefer calling a main(argv) function directly or use a subprocess.

def main(argv=None):
    args = parser.parse_args(argv)
    return execute(args)

if __name__ == "__main__":
    raise SystemExit(main())

This makes testing straightforward and keeps the entry point thin.

Testing module execution

Runpy can verify that a trusted package works as -m.

result = runpy.run_module(
    "my_package",
    run_name="__main__",
)

If the CLI changes global state, launches processes, or exits, a subprocess is a more realistic test.

Use a subprocess for fidelity

subprocess.run(
    [sys.executable, "-m", "my_package"],
    check=True,
)

This separates arguments, modules, signals, environment, and exit status.

Performance

Repeated calls may reuse import caches, but the module body is executed again.

Do not use runpy as a high-frequency call mechanism between components. Import and call a function instead.

Plugins

Runpy is rarely the best plugin interface. Plugins should expose APIs, entry points, or callable objects.

Executing a plugin as a complete script makes contracts, cleanup, and error handling harder.

Educational tools

A learning platform can use runpy inside a separate worker process to execute prepared examples.

Student or uploaded code still needs filesystem, network, CPU, memory, and time limits.

Internal launchers

A launcher can map approved command names to trusted modules.

Do not directly accept an arbitrary module name from a user, because any importable module could be selected.

Validate names with a mapping

COMMANDS = {
    "report": "myapp.commands.report",
    "cleanup": "myapp.commands.cleanup",
}

Apply authorization before choosing the module.

Untrusted code

Runpy executes Python with the current process privileges. It is unsuitable for unknown files, snippets, or packages without strong isolation.

Use a separate process or container with resource and access policies.

Untrusted paths

run_path() can execute any accessible script. Resolve targets inside an approved root and reject path escapes.

A shared temporary directory is not a safe execution source without ownership checks.

Working directory

Executed code may depend on the current working directory. Runpy does not create an isolated directory context.

Use absolute paths and package-resource APIs.

Logging

The module can install global handlers and duplicate them across repeated runs.

Libraries should avoid configuring root logging automatically; the application should own logging setup.

Threads and background tasks

Threads started by executed code can continue after runpy returns.

A disposable subprocess provides more predictable cleanup.

Integration with faulthandler

For trusted modules that may hang, enable Python faulthandler in the worker and enforce a deadline.

A stack dump before termination can reveal the blocked location.

Packaged applications

Frozen tools may implement module execution differently and require the target module and resources to be explicitly included.

Test runpy behavior in the final executable.

__cached__ and bytecode

The result can contain the cache path associated with the module.

Do not assume that cache exists or can be distributed. Bytecode is interpreter-version-specific.

Observability

Record logical module, application version, duration, outcome, and translated exit status. Avoid logging the entire returned namespace.

It can contain secrets and objects with expensive representations.

Concurrency tests

If runpy is used while other threads exist, test temporary sys changes, imports, logging, and signal behavior.

For independent execution, a subprocess remains the safer architecture.

Common mistakes

Common failures include treating runpy as a normal import, using alter_sys=True in multithreaded programs, assuming state is rolled back, simulating argv globally, accepting arbitrary names or paths, expecting complete cleanup, and using runpy as a sandbox.

Conclusion

runpy executes modules and scripts through Python’s own infrastructure and returns the resulting namespace. Use run_module() for importable names, run_path() for paths, and run_name="__main__" for entry-point behavior.

Prefer subprocesses when isolation, real arguments, and exit codes matter. Consult the official runpy documentation and Python pkgutil for discovering modules before selecting them.

Share:

Facebook
WhatsApp
Twitter
LinkedIn

Article content

    Related articles

    A developer typing code on a laptop with a Python book beside in an office.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python pkgutil: Discover Packages

    Learn Python pkgutil to list modules, walk packages, discover plugins, inspect importers, and read package resources safely.

    Ler mais

    Tempo de leitura: 6 minutos
    27/08/2026
    Colorful stacked shipping containers at Hamburg port, showcasing global trade and logistics.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python modulefinder: Discover Imports

    Learn Python modulefinder to discover imports, transitive dependencies, missing modules, paths, plugins, and limitations of static analysis.

    Ler mais

    Tempo de leitura: 8 minutos
    27/08/2026
    Detailed view of code and file structure in a software development environment.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python py_compile: Compile One File

    Learn Python py_compile to compile one file, control .pyc output, logical filenames, optimization, hash invalidation, and build errors.

    Ler mais

    Tempo de leitura: 7 minutos
    27/08/2026
    A developer typing code on a laptop with a Python book beside in an office.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python compileall: Generate .pyc Bytecode

    Learn Python compileall to generate .pyc files, validate syntax, compile in parallel, control optimization, paths, and reproducible builds.

    Ler mais

    Tempo de leitura: 7 minutos
    27/08/2026
    Vivid close-up of code on a computer screen showcasing programming details.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python codeop: Compile Interactive Input

    Learn Python codeop to detect complete, incomplete, or invalid commands, build REPLs, and preserve __future__ flags safely per session.

    Ler mais

    Tempo de leitura: 6 minutos
    27/08/2026
    A developer typing code on a laptop with a Python book beside in an office.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python linecache: Read Source Lines

    Learn Python linecache to retrieve source lines, refresh cached files, support tracebacks and loaders, preserve indentation, and secure paths.

    Ler mais

    Tempo de leitura: 7 minutos
    27/08/2026