Llevas meses siendo el único que ejecuta ese script. Conecta contra la API de Dynatrace, saca entidades monitorizadas, problemas abiertos, algún dato de consumo de host units, y genera el informe que el cliente espera cada lunes. Lo escribiste tú, en tu portátil, con tu virtualenv, tus versiones exactas de requests y python-dotenv. Nadie más lo ha tocado. Y ahora te vas dos semanas de vacaciones.
No es buena práctica —lo sabes tú y lo sabe cualquiera que lea esto—, pero es exactamente lo que pasa en la mayoría de equipos pequeños: el conocimiento se concentra en quien construyó la herramienta, y compartir «el script» no es tan simple como parece. Enviar el .py por Slack no sirve de mucho si el compañero que se queda tiene otra versión de Python instalada, no tiene permisos para instalar paquetes en su equipo, o directamente no quiere lidiar con un entorno virtual para ejecutar dos líneas de código una vez a la semana.
Aquí es donde entran los empaquetadores de ejecutables Python: herramientas que convierten tu script y sus dependencias en algo que otra persona puede ejecutar sin instalar nada. La promesa es sencilla. Cumplirla, no tanto, porque las tres opciones más usadas —PyInstaller, Nuitka y shiv— resuelven el problema de formas muy distintas, con compromisos que solo se ven con datos reales en la mano.
Por qué distribuir Python no es como distribuir un binario
Un ejecutable de Go o Rust se compila a código máquina y ya está: una sola pieza, sin dependencias externas de tiempo de ejecución. Python es un lenguaje interpretado, así que «compartir el script» implica compartir también el intérprete, la versión exacta de las librerías (requests, en nuestro caso), y a menudo detalles de sistema que ni siquiera recordamos que existen. Ahí es donde aparecen los «funciona en mi máquina» que tanto conocemos.
Tres formas de resolverlo
- PyInstaller empaqueta el intérprete de Python, el bytecode de tu script y todas sus dependencias en un único ejecutable.
- Nuitka va un paso más allá: traduce tu código Python a C real y lo compila con gcc, produciendo un binario nativo.
- shiv empaqueta tu código y sus dependencias en un archivo
.pyz(zipapp), pero sigue delegando en el Python que exista en la máquina destino.
Para comparar las tres de forma justa, construí un mini CLI de unas 25 líneas que hace una petición HTTP y mide el tiempo de respuesta —el mismo tipo de lógica que tendría tu script contra la API de Dynatrace, simplificado para el experimento—:
python
import sys, time, requests
def check(url: str) -> None:
start = time.time()
try:
resp = requests.get(url, timeout=5)
elapsed = (time.time() - start) * 1000
print(f"{url} -> {resp.status_code} ({elapsed:.0f} ms)")
except requests.RequestException as e:
print(f"{url} -> ERROR: {e}")
Con una dependencia externa real (requests), para que el empaquetado de librerías no sea un caso de laboratorio artificial.
Los resultados, con datos reales
| PyInstaller | Nuitka | shiv | |
|---|---|---|---|
| Tamaño del artefacto | 15 MB | 46 MB | 627 KB |
| Tiempo de build | ~13 s | ~264 s | ~6 s |
| Arranque en frío | ~0,40 s | ~0,19 s | ~0,30 s (primera vez), ~0,15 s después |
| Dependencia extra en el build | ninguna | patchelf (Linux) + gcc | ninguna |
| ¿Necesita Python en el destino? | No | No | Sí |
Dos cosas rompen la intuición habitual. La primera: se suele asumir que «compilar a C» produce binarios más pequeños que «empaquetar bytecode», y aquí pasó justo lo contrario —Nuitka fue el más pesado de los tres, no el más ligero—. La segunda: Nuitka necesitó una dependencia de sistema, patchelf, que tuve que instalar aparte antes de que el build funcionara; algo que PyInstaller no pide en ningún momento.
El tiempo de build de Nuitka también merece una advertencia: 264 segundos para un script de 25 líneas. Con un CLI real de varios cientos de líneas, cada iteración durante el desarrollo se convierte en una espera considerable, algo a tener muy en cuenta si vas a estar ajustando el script con frecuencia.
La prueba que de verdad importa
Todo lo anterior son números. La pregunta que de verdad le interesa al compañero que se queda cubriendo tus vacaciones es una sola: ¿funciona en una máquina sin Python instalado? Para comprobarlo sin montar un contenedor, basta con vaciar el PATH y quitar python3 de la ecuación:
bash
export PATH="/tmp/empty-path"
./checkurl-pyinstaller https://pypi.org # -> 200 (66 ms)
./checkurl-nuitka https://pypi.org # -> 200 (61 ms)
./checkurl-shiv.pyz https://pypi.org # -> /usr/bin/env: 'python3': No such file or directory
PyInstaller y Nuitka sobreviven sin problema: llevan el intérprete dentro. shiv falla, porque su primera línea es literalmente #!/usr/bin/env python3 —sigue necesitando que el sistema destino tenga Python instalado, aunque sea con otra versión de las librerías—. Es la diferencia real entre «autocontenido» y «empaquetado ligero», y solo se ve poniéndolo a prueba.
Cuál usar en cada caso
Para el escenario del compañero cubriendo tus vacaciones —entregar el script como un .exe o binario Linux que cualquiera ejecute con doble clic o ./informe-dynatrace, sin preguntarse si tiene Python—, PyInstaller es la opción por defecto: comunidad enorme, sin dependencias raras de sistema, y un tamaño razonable para un script que se ejecuta unas pocas veces por semana.
Nuitka tiene sentido cuando el arranque rápido importa de verdad: un script que se invoca cientos de veces al día desde un pipeline de CI/CD, por ejemplo, donde esos 200 milisegundos extra de PyInstaller se acumulan. El coste de un build de cuatro minutos se justifica si el ejecutable se va a usar miles de veces después.
shiv encaja cuando controlas el entorno destino —el mismo servidor, el mismo contenedor base con Python garantizado— y priorizas velocidad de construcción y tamaño mínimo sobre portabilidad total. Es la opción correcta para distribuir dentro de tu propia infraestructura, no para entregarle algo a un cliente cuyo sistema desconoces.
Ese script de Dynatrace que solo tú sabes ejecutar puede convertirse, con cualquiera de estas tres herramientas, en algo que tu equipo —o tu cliente— use sin depender de que estés localizable. La elección entre ellas ya no es una cuestión de gustos, sino de qué pregunta te haces primero: ¿necesito velocidad de arranque, velocidad de build, o simplemente que funcione en una máquina que no controlo?
Para seguir aprendiendo:
- Documentación oficial de PyInstaller
- Documentación de usuario de Nuitka
- Documentación oficial de shiv

