Python runpy: Execute Modules and Paths

Published on: August 13, 2026
Reading time: 5 minutes
Executing code representing modules and paths run with Python runpy

Python runpy locates and executes code through the module system. It implements part of the behavior behind python -m package.module and can also run scripts, directories, and ZIP archives containing a top-level __main__.py.

The module is useful for launchers, testing tools, automation, and systems that need to execute an entry point and receive the resulting globals dictionary. It is not a sandbox. Code runs in the current process and may modify files, networks, environment variables, import caches, logging, signals, threads, and other global state.

Execute a module by name

run_module() accepts an absolute module name and locates it through the normal import machinery.

import runpy

result = runpy.run_module('my_app.diagnostics')
print(result.keys())

The code executes in a fresh namespace and the function returns that dictionary. This differs from a normal import, which creates or reuses a persistent entry in sys.modules.

Execute a package

When the name refers to a package, run_module() finds and executes package.__main__.

result = runpy.run_module('my_app')
# executes my_app.__main__

This reproduces the core behavior of python -m my_app. The parent package may be imported during discovery, so its initializer can still produce side effects.

Special globals

Before execution, runpy defines values such as __name__, __spec__, __file__, __cached__, __loader__, and __package__.

result = runpy.run_module(
    'my_app.diagnostics',
    run_name='__main__',
)
print(result['__name__'])

__spec__.name still identifies the real module even when run_name changes. Since Python 3.12, direct setting of some legacy globals is deprecated; code should prefer the module spec.

Pre-populate the namespace

init_globals supplies initial values without modifying the original mapping.

context = {
    'SETTINGS': {'mode': 'test'},
    'SERVICE': fake_service,
}

result = runpy.run_module(
    'my_app.job',
    init_globals=context,
)

Special values controlled by runpy override matching entries. Supplying powerful objects also gives executed code access to their capabilities.

Trigger script-style behavior

Many modules use the familiar guard:

if __name__ == '__main__':
    main()

Set run_name='__main__' to trigger it.

runpy.run_module(
    'my_app.cli',
    run_name='__main__',
)

This executes the module like a script but remains inside the current process. Functions and classes in the returned dictionary are not guaranteed to behave correctly after runpy returns; use a normal import when you need reusable APIs.

alter_sys and -m compatibility

With alter_sys=True, run_module() temporarily updates sys.argv[0] and installs a temporary module object in sys.modules.

result = runpy.run_module(
    'my_app.cli',
    run_name='__main__',
    alter_sys=True,
)

This more closely resembles command-line execution, but it is not thread-safe. Other threads may observe changed arguments or a partially initialized module. Leave alter_sys=False in threaded services or delegate execution to another process.

Execute a filesystem path

run_path() executes code at a named location.

import runpy

result = runpy.run_path('scripts/report.py')
print(result.get('RESULT'))

The path may refer to source code, compiled bytecode, or a valid sys.path entry such as a directory or ZIP containing __main__.py.

Run directories and ZIP applications

For a path entry, runpy temporarily places it at the start of sys.path and searches for __main__.

runpy.run_path('dist/tool.pyz', run_name='__main__')

This works with artifacts created by Python zipapp. Validate the archive first: if the specified location lacks __main__, another module of that name elsewhere on sys.path may be found.

run_path always alters sys

Running a directory or ZIP requires temporary changes to sys.path, sys.argv[0], and sys.modules. The values are restored later, but other threads can observe the intermediate state.

Serialize such calls or, preferably, run the target in a subprocess. A separate process also provides timeouts and failure isolation.

runpy is not a sandbox

Executed code has ordinary Python and operating-system access.

from pathlib import Path
Path('/tmp/marker').write_text('executed')

Removing a few names from init_globals does not isolate the code. For third-party input, use a disposable process or container, an unprivileged user, a restricted filesystem, disabled networking, and resource quotas.

Side effects remain

The primary globals dictionary is fresh, but imports performed during execution remain cached in sys.modules. Environment changes, handlers, background threads, signal configuration, and library state may also persist.

When you need repeatable clean execution, a process boundary is more reliable than attempting to undo every effect.

runpy or importlib?

Use runpy when the goal is to execute an entry point with script semantics. Use importlib.import_module() when you want a module object and supported access to its functions and classes.

The documentation warns that definitions created by runpy may not work correctly after the function returns. For static import discovery, see Python modulefinder.

runpy or subprocess?

Runpy is fast and shares memory, but it also shares failures and global state. A subprocess offers a real argument vector, exit status, timeout, output capture, and isolation.

import subprocess
import sys

subprocess.run(
    [sys.executable, '-m', 'my_app.cli'],
    check=True,
    timeout=30,
)

Prefer subprocess for untrusted plugins, long-running tasks, concurrent execution, or code that might terminate the interpreter.

Read results from globals

The returned dictionary may expose values produced by a script.

result = runpy.run_path(
    'calculation.py',
    init_globals={'INPUT': 21},
)
print(result['OUTPUT'])

Define a small explicit contract, including required names and expected types. Do not serialize the entire namespace because it may contain modules, functions, files, and sensitive objects.

Handle SystemExit

Executed code may call sys.exit(), which raises SystemExit.

try:
    runpy.run_module('my_app.cli', run_name='__main__')
except SystemExit as exc:
    exit_code = exc.code

Decide whether the code should propagate, become a result, or be logged as a failure. Handle KeyboardInterrupt at the outer boundary as well.

Exceptions and diagnostics

Import, syntax, and runtime errors propagate. Record the target module or path, Python version, and sanitized arguments.

Use the techniques from Python traceback for useful internal diagnostics while keeping sensitive details away from external users.

Controlled task launchers

An internal tool may map friendly actions to approved module names.

TASKS = {
    'migrate': 'my_app.migrations.apply',
    'verify': 'my_app.diagnostics',
}

module = TASKS[action]
runpy.run_module(module, run_name='__main__')

Use an allowlist. Never accept arbitrary importable names or filesystem paths from a request.

Concurrency

Even with alter_sys=False, executed code may mutate process-global state. With alter_sys=True or run_path(), thread-safety concerns increase.

In asynchronous applications, avoid heavy execution on the event loop. Use process workers and limit concurrent jobs.

Test a runpy wrapper

Create small temporary scripts with explicit contracts.

def test_run_path(tmp_path):
    script = tmp_path / 'job.py'
    script.write_text('OUTPUT = INPUT * 2\n', encoding='utf-8')

    result = runpy.run_path(
        str(script),
        init_globals={'INPUT': 5},
    )
    assert result['OUTPUT'] == 10

Test SystemExit, missing modules, exceptions, ZIPs without __main__, global-state changes, and concurrent calls.

Relationship to code and codeop

Python code creates persistent REPLs. Codeop detects incomplete interactive input. Runpy executes complete units identified by a module name or path.

Choose the abstraction that matches the experience. Do not use runpy as an improvised interactive sandbox.

Common mistakes

  • Treating runpy as a sandbox.
  • Using alter_sys=True in multithreaded code.
  • Executing an unvalidated path.
  • Assuming every side effect is reverted.
  • Reusing returned definitions without guarantees.
  • Accepting arbitrary module names from users.
  • Ignoring SystemExit and timeouts.

Best practices

  • Use allowlists for modules and paths.
  • Prefer importlib for reusable APIs.
  • Prefer subprocess for isolation.
  • Avoid temporary sys changes in threads.
  • Validate ZIPs and __main__.py.
  • Define a small result contract.
  • Log failures without secrets.

Conclusion

Python runpy programmatically executes modules and filesystem paths with semantics similar to python -m and script execution. It supports packages, directories, ZIPs, and returned globals.

Use it only for trusted code in the current process. For concurrency, isolation, and security, prefer subprocesses. Consult the official runpy documentation and the documentation for the -m option.

Share:

Facebook
WhatsApp
Twitter
LinkedIn

Article content

    Related articles

    Software package representing module discovery with Python pkgutil
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python pkgutil: Discover Packages

    Learn Python pkgutil to discover modules, walk packages, resolve objects, extend package paths, and access resources safely.

    Ler mais

    Tempo de leitura: 5 minutos
    13/08/2026
    Binary code network representing the import graph analyzed with Python modulefinder
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python modulefinder: Analyze Imports

    Learn Python modulefinder to map imports, detect missing modules, customize search paths, and audit dependencies with clear limitations.

    Ler mais

    Tempo de leitura: 5 minutos
    13/08/2026
    Organized binders representing applications packaged as executable .pyz files with Python zipapp
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python zipapp: Build Executable .pyz Files

    Learn Python zipapp to package applications as executable .pyz files, define entry points, bundle dependencies, and distribute safely.

    Ler mais

    Tempo de leitura: 5 minutos
    13/08/2026
    Code editor representing REPL completion with Python rlcompleter
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python rlcompleter: REPL Completion

    Learn Python rlcompleter to add completion to REPLs, consoles, and editors, control namespaces, filter results, and avoid side effects.

    Ler mais

    Tempo de leitura: 5 minutos
    13/08/2026
    Terminal window representing an interactive console built with Python cmd
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python cmd: Build Interactive Consoles

    Learn Python cmd to build interactive consoles with commands, help, history, completion, testing, streams, and secure action control.

    Ler mais

    Tempo de leitura: 4 minutos
    12/08/2026
    Interactive terminal representing a custom REPL built with the Python code module
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python code: Build a Custom REPL

    Learn the Python code module to build custom REPLs, control namespaces, prompts, output, incomplete blocks, errors, and local exit behavior.

    Ler mais

    Tempo de leitura: 5 minutos
    12/08/2026