El módulo runpy en Python localiza y ejecuta código mediante el sistema de módulos. Implementa parte del comportamiento de python -m paquete.modulo y también puede ejecutar scripts, directorios y archivos ZIP que contengan un __main__.py en la raíz.
Es útil en launchers, pruebas, automatizaciones y sistemas que necesitan ejecutar un entry point y recibir el diccionario global resultante. No es un sandbox. El código se ejecuta en el proceso actual y puede modificar archivos, red, variables de entorno, caches de import, logging, señales, threads y otros estados globales.
Ejecutar un módulo por nombre
run_module() recibe un nombre absoluto y localiza el código con el mecanismo normal de imports.
import runpy
resultado = runpy.run_module('mi_app.diagnostico')
print(resultado.keys())El código se ejecuta en un namespace nuevo y la función devuelve ese diccionario. Esto difiere de un import normal, que crea o reutiliza una entrada persistente en sys.modules.
Ejecutar un paquete
Cuando el nombre corresponde a un paquete, run_module() busca y ejecuta paquete.__main__.
resultado = runpy.run_module('mi_app')
# ejecuta mi_app.__main__Esto reproduce el núcleo de python -m mi_app. El paquete padre puede importarse durante la búsqueda, por lo que su inicializador puede producir efectos.
Variables globales especiales
Antes de ejecutar, runpy define __name__, __spec__, __file__, __cached__, __loader__ y __package__.
resultado = runpy.run_module(
'mi_app.diagnostico',
run_name='__main__',
)
print(resultado['__name__'])__spec__.name continúa identificando el módulo real aunque cambie run_name. Desde Python 3.12, la asignación directa de algunos valores heredados está deprecada; conviene consultar la especificación del módulo.
Preconfigurar el namespace
init_globals proporciona valores iniciales sin modificar el mapping original.
contexto = {
'CONFIGURACION': {'modo': 'test'},
'SERVICIO': servicio_falso,
}
resultado = runpy.run_module(
'mi_app.tarea',
init_globals=contexto,
)Los nombres especiales controlados por runpy sustituyen valores coincidentes. Entregar objetos poderosos al código también amplía sus capacidades.
Activar comportamiento de script
Muchos módulos contienen:
if __name__ == '__main__':
main()Configura run_name='__main__' para ejecutar ese bloque.
runpy.run_module(
'mi_app.cli',
run_name='__main__',
)El módulo se comporta como script, pero permanece dentro del proceso. Las funciones y clases del diccionario devuelto no tienen garantía de funcionar correctamente después del retorno; usa import normal cuando quieras reutilizar APIs.
alter_sys y compatibilidad con -m
Con alter_sys=True, run_module() cambia temporalmente sys.argv[0] e instala un módulo temporal en sys.modules.
resultado = runpy.run_module(
'mi_app.cli',
run_name='__main__',
alter_sys=True,
)Esto se aproxima a la CLI, pero no es thread-safe. Otros threads pueden observar argumentos modificados o un módulo parcialmente inicializado. En servidores multithread, deja alter_sys=False o usa otro proceso.
Ejecutar una ruta con run_path()
run_path() ejecuta código en una ubicación del sistema de archivos.
import runpy
resultado = runpy.run_path('scripts/informe.py')
print(resultado.get('RESULTADO'))La ruta puede apuntar a código fuente, bytecode o una entrada válida de sys.path, como un directorio o ZIP con __main__.py.
Ejecutar directorios y ZIPs
Para una entrada de ruta, runpy la añade temporalmente al principio de sys.path y busca __main__.
runpy.run_path('dist/herramienta.pyz', run_name='__main__')Esto funciona con archivos creados por zipapp en Python. Valida el archivo: si no contiene __main__, la búsqueda puede encontrar otro módulo de ese nombre en sys.path.
run_path altera sys obligatoriamente
Ejecutar un directorio o ZIP requiere cambios temporales en sys.path, sys.argv[0] y sys.modules. Los valores se restauran, pero otros threads pueden ver el estado intermedio.
Serializa llamadas o, preferiblemente, usa un subprocesso. Un proceso separado también proporciona timeout y aislamiento de fallos.
runpy no es sandbox
El código ejecutado tiene acceso normal a Python y al sistema operativo.
from pathlib import Path
Path('/tmp/marcador').write_text('ejecutado')Eliminar algunos nombres de init_globals no aísla el código. Para código de terceros, usa proceso o container desechable, usuario sin privilegios, sistema de archivos limitado, red bloqueada y cuotas.
Efectos persistentes
El namespace principal es nuevo, pero los imports realizados quedan en sys.modules. Cambios de entorno, handlers, threads de fondo, señales y estado de bibliotecas también pueden permanecer.
Cuando necesitas ejecución limpia y repetible, una frontera de proceso es más fiable que intentar revertir cada efecto.
runpy o importlib
Usa runpy para ejecutar un entry point con semántica de script. Utiliza importlib.import_module() cuando quieras un módulo y acceso soportado a sus funciones y clases.
La documentación advierte que las definiciones creadas por runpy pueden no funcionar después. Para descubrir imports estáticamente, consulta modulefinder en Python.
runpy o subprocess
Runpy es rápido y comparte memoria, pero también comparte fallos y estado global. Un subprocesso ofrece vector de argumentos, código de salida, timeout, captura de salida y aislamiento.
import subprocess
import sys
subprocess.run(
[sys.executable, '-m', 'mi_app.cli'],
check=True,
timeout=30,
)Prefiere subprocess para plugins no confiables, tareas largas, ejecución concurrente o código que pueda terminar el intérprete.
Leer resultados del namespace
El diccionario devuelto puede incluir valores producidos por el script.
resultado = runpy.run_path(
'calculo.py',
init_globals={'ENTRADA': 21},
)
print(resultado['SALIDA'])Define un contrato pequeño, con nombres y tipos esperados. No serialices todo el namespace: puede contener módulos, funciones, archivos y objetos sensibles.
Tratar SystemExit
El código puede llamar sys.exit(), que genera SystemExit.
try:
runpy.run_module('mi_app.cli', run_name='__main__')
except SystemExit as salida:
codigo = salida.codeDecide si propagar el código, convertirlo en resultado o registrarlo como fallo. Trata también KeyboardInterrupt en el límite exterior.
Excepciones y diagnóstico
Los errores de import, sintaxis y runtime se propagan. Registra módulo o ruta, versión de Python y argumentos sanitizados.
Usa las técnicas de traceback en Python para diagnósticos internos, sin mostrar detalles sensibles al usuario.
Launchers controlados
Una herramienta interna puede mapear acciones amigables a módulos aprobados.
TAREAS = {
'migrar': 'mi_app.migraciones.aplicar',
'verificar': 'mi_app.diagnostico',
}
modulo = TAREAS[accion]
runpy.run_module(modulo, run_name='__main__')Usa una allowlist. No aceptes cualquier nombre importable o ruta del usuario.
Concurrencia
Incluso con alter_sys=False, el código puede modificar estado global. Con alter_sys=True o run_path(), el riesgo aumenta.
En aplicaciones asíncronas, no ejecutes tareas pesadas en el event loop. Usa workers de proceso y limita trabajos simultáneos.
Probar una capa basada en runpy
Crea scripts temporales pequeños.
def test_run_path(tmp_path):
script = tmp_path / 'job.py'
script.write_text('SALIDA = ENTRADA * 2\n', encoding='utf-8')
resultado = runpy.run_path(
str(script),
init_globals={'ENTRADA': 5},
)
assert resultado['SALIDA'] == 10Prueba SystemExit, módulos ausentes, excepciones, ZIPs sin __main__, cambios globales y llamadas concurrentes.
Relación con code y codeop
code en Python crea REPLs persistentes. Codeop detecta entrada interactiva incompleta. Runpy ejecuta unidades completas por nombre o ruta.
Elige la abstracción correcta. No uses runpy como sandbox interactivo improvisado.
Errores frecuentes
- Tratar runpy como sandbox.
- Usar
alter_sys=Trueen código multithread. - Ejecutar una ruta no validada.
- Suponer que todos los efectos se revierten.
- Reutilizar definiciones sin garantía.
- Aceptar cualquier nombre de módulo.
- Ignorar SystemExit y timeouts.
Buenas prácticas
- Usa allowlists de módulos y rutas.
- Prefiere importlib para APIs reutilizables.
- Prefiere subprocess para aislamiento.
- Evita cambios de
sysen threads. - Valida ZIPs y
__main__.py. - Define un contrato mínimo de resultados.
- Registra fallos sin secretos.
Conclusión
runpy en Python ejecuta módulos y rutas con semántica cercana a python -m y a los scripts. Soporta paquetes, directorios, ZIPs y devuelve los globals resultantes.
Úsalo solo para código confiable en el proceso actual. Para concurrencia, aislamiento y seguridad, prefiere subprocessos. Consulta la documentación oficial de runpy y la documentación de la opción -m.







