runpy en Python: ejecuta módulos

Publicado el: 13/08/2026
Tempo de leitura: 6 minutos
Código en ejecución que representa módulos y rutas ejecutados con runpy en Python

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.code

Decide 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'] == 10

Prueba 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=True en 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 sys en 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.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    Desarrollador configurando un servidor HTTPS y certificado TLS con Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    HTTPSServer: crea un servidor HTTPS local en Python

    Aprende HTTPSServer en Python para crear servicios HTTPS locales, configurar certificados, usar hilos y comprender sus límites.

    Ler mais

    Tempo de leitura: 4 minutos
    08/10/2026
    Archivos protegidos que representan extracción segura de TAR con Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    tarfile extraction_filter: extrae TAR con seguridad

    Aprende tarfile extraction_filter en Python para extraer archivos TAR con validación, seguridad y control de rutas.

    Ler mais

    Tempo de leitura: 6 minutos
    08/10/2026
    Código Python mostrando avisos controlados con catch_warnings
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    catch_warnings: captura warnings en pruebas Python

    Aprende catch_warnings en Python para capturar, probar y controlar avisos con filtros específicos y alcance seguro.

    Ler mais

    Tempo de leitura: 5 minutos
    07/10/2026
    Código Python que representa referencias persistentes de pickle
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    pickle persistent_id: serializa referencias externas

    Aprende pickle persistent_id en Python para referencias externas estables, validación, seguridad, rendimiento y compatibilidad.

    Ler mais

    Tempo de leitura: 5 minutos
    07/10/2026
    Código binario que representa buffers y vistas de memoria en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    memoryview.count: cuenta valores sin copiar buffers

    Aprende memoryview.count en Python para contar bytes y valores en buffers sin copias, con formatos, límites y buenas prácticas.

    Ler mais

    Tempo de leitura: 6 minutos
    06/10/2026
    Código Python y anotaciones de tipos
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    annotationlib: evita imports circulares en anotaciones

    Aprende annotationlib en Python para inspeccionar anotaciones diferidas, evitar imports circulares y crear herramientas seguras.

    Ler mais

    Tempo de leitura: 6 minutos
    06/10/2026