El módulo zipapp empaqueta una aplicación Python en un único archivo ZIP ejecutable, normalmente con extensión .pyz. Python añade el archivo a sys.path y ejecuta su __main__.py. Es útil para herramientas internas, scripts de automatización, utilidades CLI, prototipos y distribución en sistemas que ya tienen un runtime compatible.
Un zipapp no es un ejecutable nativo independiente. No incluye automáticamente Python, no resuelve todas las dependencias ni hace importables extensiones compiladas desde dentro del ZIP. Para distribución pública, desktop o equipos sin Python, una herramienta de packaging completa puede ser más apropiada.
Estructura mínima
El archive necesita un __main__.py en la raíz.
mi_app/
├── __main__.py
└── paquete/
├── __init__.py
└── cli.py
El entry file inicia la aplicación.
from paquete.cli import main
if __name__ == "__main__":
raise SystemExit(main())
Así main() puede probarse de forma independiente y devolver un exit code.
Crea desde línea de comandos
python -m zipapp mi_app -o mi_app.pyz
Ejecútalo con:
python mi_app.pyz --help
Python trata el archive como directorio importable y ejecuta el entry point.
Crea con la API
zipapp.create_archive() automatiza el build.
from zipapp import create_archive
create_archive(
"mi_app",
target="dist/mi_app.pyz",
)
Crea primero el directorio de destino y usa pathlib para organizar el pipeline.
Genera un entry point
Si no existe __main__.py, indica una función como paquete.modulo:funcion.
python -m zipapp mi_app \
-m "paquete.cli:main" \
-o mi_app.pyz
La herramienta genera un archivo que importa y llama la función.
Diseño de main
La función debe ser importable y callable sin argumentos posicionales, salvo que lea sys.argv.
def main():
argumentos = parser.parse_args()
ejecutar(argumentos)
return 0
No ejecutes todo el programa durante el import. Los imports deberían definir componentes.
Shebang e interpreter
La opción --python o el parámetro interpreter añade una línea de intérprete.
python -m zipapp mi_app \
--python "/usr/bin/env python3" \
-o mi_app.pyz
chmod +x mi_app.pyz
./mi_app.pyz
Funciona en sistemas con shebang. En Windows normalmente se usa python mi_app.pyz.
Versión del runtime
Un shebang genérico puede resolver a versiones diferentes. Valida la mínima al iniciar.
import sys
if sys.version_info < (3, 12):
raise SystemExit("Se requiere Python 3.12 o superior")
Documenta las versiones probadas.
Compresión
--compress o compressed=True comprime las entradas.
create_archive(
"mi_app",
target="dist/mi_app.pyz",
compressed=True,
)
Reduce tamaño, pero añade CPU. En aplicaciones pequeñas la diferencia puede ser mínima.
Build desde fuente limpia
La API puede procesar archives existentes en ciertos flujos, pero no debería tratarse como editor ZIP genérico.
Reconstruye desde una fuente limpia para trazabilidad y reproducibilidad.
Dependencias Python puras
Los paquetes escritos solo en Python pueden instalarse en un staging directory.
python -m pip install \
--target build/app \
--requirement requirements.txt
cp -r src/mi_paquete build/app/
python -m zipapp build/app -o dist/app.pyz
Usa entorno aislado y versiones fijadas.
Extensiones nativas
Módulos .so y .pyd normalmente no pueden importarse directamente desde el ZIP porque el loader necesita un archivo real.
Mantén dependencias nativas fuera, extráelas de forma controlada o elige otra herramienta.
Detecta dependencias nativas
No confíes solo en el nombre. Inspecciona wheels y prueba en un entorno limpio.
Una dependencia transitiva puede introducir código nativo.
Recursos del paquete
El código dentro del ZIP no debe asumir que __file__ es un path normal.
Usa importlib.resources.
from importlib.resources import files
texto = (
files("mi_paquete")
.joinpath("datos/default.json")
.read_text(encoding="utf-8")
)
Recursos que necesitan path
importlib.resources.as_file() puede materializar temporalmente un recurso.
Úsalo con context manager y no guardes el path después.
Archivos escribibles
Trata el contenido del archive como read-only. No guardes configuración, cache o base junto a los módulos internos.
Usa directorios de usuario, cache o temporales.
Directorio actual
No supongas que el proceso empieza en el directorio del archive. Path.cwd() depende del lugar de ejecución.
Usa paths absolutos configurados o recursos internos.
Imports
Organiza la aplicación como paquete y prefiere imports absolutos. Los nombres que coinciden con la biblioteca estándar causan conflictos.
Prueba el artefacto final.
Namespace packages
Pueden funcionar, pero mezclar partes internas y externas requiere pruebas.
Un paquete normal suele ser más predecible.
Comportamiento de sys.path
El archive aparece en el import path y puede interactuar con paquetes instalados.
Usa nombres únicos y no dependas de shadowing accidental.
Conflictos externos
Si una dependencia no está incluida, la aplicación puede importar una versión cualquiera del entorno.
Valida versiones o distribuye instrucciones para un venv dedicado.
Uso con venv
Una estrategia interna es crear un venv con Python y dependencias nativas y ejecutar el .pyz con ese intérprete.
El archive contiene código y el venv aporta runtime y bibliotecas.
zipapp frente a pipx
pipx instala CLIs en entornos aislados. Un paquete convencional con entry point puede ser más fácil de actualizar.
Elige zipapp cuando el archivo único simplifique realmente la operación.
Metadatos de versión
Expón --version e incluye opcionalmente un manifest con commit, fecha y lock de dependencias.
__version__ = "1.4.0"
Build reproducible
Los ZIP guardan timestamps y orden, produciendo hashes distintos. Controla timestamps, ordena entradas y fija dependencias.
Puede ser necesaria una etapa adicional para determinismo estricto.
Filtro de archivos
La API acepta un filtro.
def incluir(ruta):
partes = set(ruta.parts)
return not partes.intersection({"__pycache__", ".git", "tests"})
create_archive("build/app", "dist/app.pyz", filter=incluir)
No excluyas recursos necesarios. Ejecuta el resultado en un directorio limpio.
No incluyas secretos
Un zipapp es un ZIP común y puede abrirse fácilmente. Nunca guardes passwords, tokens, private keys o credenciales.
Carga secretos desde un manager o configuración externa protegida.
Checksums y firmas
Distribuye hash o firma. Python no valida automáticamente una firma antes de ejecutar.
El proceso de deployment debe verificar el archivo.
Archives no confiables
Ejecutar un .pyz ejecuta código con los permisos del usuario. No descargues y ejecutes archives desconocidos.
Usa HTTPS, firmas, origen confiable y mínimo privilegio.
Actualizaciones atómicas
Descarga con un nombre temporal, valida y usa os.replace().
No sobrescribas la versión activa antes de verificar.
Rollback
Conserva la versión anterior hasta que un smoke test pase. Un launcher o symlink puede seleccionar la activa.
No combines migraciones irreversibles con actualización sin rollback.
Containers
Un zipapp puede reducir archivos en una imagen, pero sigue necesitando Python y dependencias.
Evalúa si agrega valor sobre las layers del container.
Argumentos y exit codes
Usa argparse y códigos consistentes. raise SystemExit(main()) propaga el resultado.
Salida normal en stdout y errores en stderr.
Logging
No escribas logs dentro del archive. Usa stderr, archivo externo o logging estructurado.
Registra la versión al iniciar.
Pruebas
Construye en CI y ejecuta comandos reales en un entorno limpio. Prueba help, version, dependencias ausentes, recursos, códigos, paths con espacios, Windows y POSIX.
Compara fuente y .pyz.
Errores comunes
Los fallos frecuentes son olvidar __main__.py, incluir extensiones nativas, tratar __file__ como path normal, escribir dentro del archive, depender de versiones externas desconocidas, incluir secretos, no probar el artefacto y confundir zipapp con ejecutable autónomo.
Conclusión
zipapp empaqueta aplicaciones Python puras en un único archive ejecutado por Python. Usa un entry point limpio, importlib.resources, dependencias fijadas, contenido read-only y tests del artefacto.
Consulta la documentación oficial de zipapp y sysconfig en Python para comprender el runtime de destino.







