Apariencia
📙 Clase 34 — Empaquetar y distribuir: PyInstaller
Fase 6 · Especialización: Apps de escritorio 🎯 ⬅️ Volver al índice de clases
🎯 Qué aprendí
- Convertir mi app en un ejecutable (.app en macOS / .exe en Windows) que corre sin Python instalado.
- Los flags esenciales:
--windowed,--onefile,--name,--icon. - El gotcha específico de CustomTkinter (
--collect-all) y dónde guardar los datos.
📖 PARTE TEÓRICA
📦 1. ¿Qué hace PyInstaller?
Analiza tu main.py, junta el intérprete de Python + tus módulos + las librerías, y lo empaqueta en un ejecutable. El usuario final hace doble clic — no sabe qué es pip.
bash
pip install pyinstaller # en tu venv (instalado: 6.21.0 ✔)
pyinstaller --version # 6.21.0🚀 2. El comando para una app GUI
bash
pyinstaller --windowed --name "MiAgenda" --collect-all customtkinter main.py| Flag | Qué hace (del --help real de 6.21.0) | ¿Cuándo? |
|---|---|---|
-w, --windowed, --noconsole | sin ventana de consola negra detrás | siempre en apps GUI |
-n, --name NAME | nombre del ejecutable y la carpeta | siempre |
--collect-all customtkinter | incluye TODOS los archivos del paquete | CTk lo necesita (ver §3) |
-F, --onefile | un único archivo ejecutable | distribución simple (arranca más lento) |
-i, --icon archivo | .icns (macOS) / .ico (Windows) | toque final |
-y, --noconfirm | sobreescribe dist/ sin preguntar | al re-empaquetar |
Resultado:
tu_proyecto/
├── build/ 🗑️ archivos temporales (ignorar)
├── dist/
│ └── MiAgenda.app 🎉 LO QUE DISTRIBUYES (macOS; en Windows: MiAgenda.exe)
└── MiAgenda.spec 📋 la "receta" (edítala para builds avanzados y re-usa: pyinstaller MiAgenda.spec)⚠️ 3. Los gotchas que te van a pasar
① CustomTkinter y sus assets. CTk trae archivos .json de temas e imágenes que PyInstaller no detecta solo (solo sigue imports de Python). Sin --collect-all customtkinter, la app empaquetada muere al abrir con FileNotFoundError de un tema.
📌 Es el problema #1 documentado en la guía oficial de empaquetado de CustomTkinter. Si también usas imágenes propias:
--add-data "imagenes:imagenes".
② Los datos NO van junto al ejecutable. Dentro de un .app/onefile, la carpeta del programa es de solo lectura (o temporal). Si guardabas app.db "al lado del .py", empaquetado falla. La solución ya la aprendiste en la Clase 22:
python
RUTA_DATOS = Path.home() / ".mi_agenda" # SIEMPRE en el home del usuario③ Empaqueta desde el mismo sistema. PyInstaller no hace cross-compile: el .app se genera en macOS, el .exe en Windows. Para ambos, necesitas ambos sistemas (o CI).
④ Antivirus/Gatekeeper. Ejecutables sin firmar generan avisos (macOS: clic derecho → Abrir la primera vez). La firma de código es el paso "distribución seria" (Apple Developer / certificado Windows).
🧪 Tip de entrevista: "¿Cómo distribuyes una app Python de escritorio?" → PyInstaller (o similares: Nuitka, cx_Freeze) empaqueta intérprete+dependencias en un ejecutable por plataforma; los datos del usuario van a su home/AppData; firmar el binario evita los avisos de seguridad.
🔁 4. El flujo de release completo
código OK ──▶ requirements.txt (pip freeze > requirements.txt)
──▶ probar: python main.py
──▶ pyinstaller --windowed --name "MiAgenda" --collect-all customtkinter main.py
──▶ probar dist/MiAgenda.app 🔑 EN UNA MÁQUINA SIN PYTHON (o cuenta limpia)
──▶ comprimir y compartir (zip / DMG / instalador)💻 PARTE PRÁCTICA — tu misión
Empaqueta tu To-Do (la versión clase + JSON o SQLite):
- Asegúrate de que los datos van a
Path.home() / ".mi_todo"(¡no junto al .py!). - En el venv:
pip install pyinstaller(ya está en el del curso). pyinstaller --windowed --name "MiToDo" --collect-all customtkinter main.py- Abre
dist/MiToDo.appcon doble clic. Agrega tareas, cierra, reabre: ¿persisten? - 📝 Documenta en 06-Errores cualquier tropiezo — los errores de empaquetado son apuntes de oro (frontmatter
categoria: "📦 Empaquetado").
🔐 Solución de referencia (ábrela solo DESPUÉS de intentarlo)
Ver solución · la sesión de terminal completa
bash
# 0) siempre desde el venv del proyecto
source .venv/bin/activate
# 1) congelar dependencias (reproducibilidad)
pip freeze > requirements.txt
# 2) probar que la app corre ANTES de empaquetar
python main.py
# 3) empaquetar (macOS; en Windows es el mismo comando)
pyinstaller --windowed --name "MiToDo" --collect-all customtkinter --noconfirm main.py
# 4) queda así:
# dist/MiToDo.app ← lo que distribuyes (Windows: dist/MiToDo/MiToDo.exe)
# build/ y MiToDo.spec ← artefactos del build
open dist/MiToDo.app # probarla como la abriría el usuario
# 5) que git ignore los artefactos → añadir a .gitignore:
# build/
# dist/
# *.specSi al abrir el .app no pasa nada, corre el binario interno para VER el error: ./dist/MiToDo.app/Contents/MacOS/MiToDo — el traceback aparece en la terminal (el clásico: falta --collect-all customtkinter, o los datos apuntaban junto al .py en vez de Path.home()).
⚠️ El primer build tarda (analiza todo) y
dist/pesa bastante (~50-80 MB): es normal — lleva un Python completo adentro. Agregabuild/,dist/y*.speca tu.gitignore.
❓ Preguntas y respuestas (autoevaluación)
1. ¿Qué contiene el ejecutable que genera PyInstaller?
El intérprete de Python + tus módulos + las dependencias: por eso corre sin Python instalado.
2. ¿Por qué CustomTkinter necesita --collect-all customtkinter?
Porque trae archivos no-Python (temas .json, imágenes) que el análisis de imports no detecta; sin ellos la app empaquetada no abre.
3. ¿Dónde debe guardar sus datos una app empaquetada y por qué?
En el home del usuario (
Path.home() / ".mi_app"): la carpeta del ejecutable es de solo lectura/temporal.
4. ¿Puedes generar el .exe de Windows desde tu Mac?
No: PyInstaller no cross-compila. Cada plataforma empaqueta lo suyo.
📎 Apuntes relacionados
Path.home()para los datos → Clase 22- La estructura main.py/ui/logica/datos que empaquetas → Clase 29
- Comandos de venv/pip → 01-Comandos
➡️ Siguiente
Clase 35 · Proyecto final — todo el curso en una app real, escrita por ti.