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__.pyThis 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.
Recommended CLI architecture
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.







