Cómo usar PyInstaller para crear ejecutables de Python

Python, potente y versátil como es, carece de algunas capacidades clave listas para usar. Por un lado, Python no proporciona ningún mecanismo nativo para compilar un programa Python en un paquete ejecutable independiente.

Para ser justos, el caso de uso original de Python nunca requirió paquetes independientes. Los programas de Python, en general, se han ejecutado in situ en sistemas donde vivía una copia del intérprete de Python. Pero la creciente popularidad de Python ha creado una mayor demanda de ejecutar aplicaciones de Python en sistemas sin tiempo de ejecución de Python instalado.

Varios terceros han diseñado soluciones para implementar aplicaciones Python independientes. La solución más popular del grupo, y la más madura, es PyInstaller. PyInstaller no hace que el proceso de empaquetar una aplicación Python sea totalmente indoloro, pero es muy útil.

En este artículo, exploraremos los conceptos básicos del uso de PyInstaller, incluido el funcionamiento de PyInstaller, cómo usar PyInstaller para crear un ejecutable de Python independiente, cómo ajustar los ejecutables de Python que cree y cómo evitar algunos de los errores comunes que surgen. con el uso de PyInstaller.

Creando un paquete PyInstaller

PyInstaller es un paquete de Python, instalado con pip( pip install pyinstaller). PyInstaller se puede instalar en su instalación de Python predeterminada, pero es mejor crear un entorno virtual para el proyecto que desea empaquetar e instalar allí PyInstaller.

PyInstaller funciona leyendo su programa Python, analizando todas las importaciones que realiza y agrupando copias de esas importaciones con su programa. PyInstaller lee su programa desde su punto de entrada. Por ejemplo, si el punto de entrada de su programa es myapp.py, correría pyinstaller myapp.pypara realizar el análisis. PyInstaller puede detectar y empaquetar automáticamente muchos paquetes comunes de Python, como NumPy, pero es posible que deba proporcionar sugerencias en algunos casos. (Más sobre esto más adelante).

Después de analizar su código y descubrir todas las bibliotecas y módulos que utiliza, PyInstaller genera un "archivo de especificaciones". Un script de Python con la extensión .spec, este archivo incluye detalles sobre cómo debe empaquetarse su aplicación de Python. La primera vez que ejecute PyInstaller en su aplicación, PyInstaller generará un archivo de especificaciones desde cero y lo completará con algunos valores predeterminados. No descarte este archivo; ¡es la clave para refinar una implementación de PyInstaller!

Finalmente, PyInstaller intenta producir un ejecutable desde la aplicación, incluido con todas sus dependencias. Cuando haya terminado, dist aparecerá una subcarpeta con el nombre (de forma predeterminada; puede especificar un nombre diferente) en el directorio del proyecto. Este, a su vez, contiene un directorio que es su aplicación incluida: tiene un .exearchivo para ejecutar, junto con todas las bibliotecas y otros archivos complementarios necesarios.

Entonces, todo lo que necesita hacer para distribuir su programa es empaquetar este directorio como un .ziparchivo o algún otro paquete. Normalmente, el paquete deberá extraerse en un directorio en el que el usuario tenga permisos de escritura para poder ejecutarse.

Probando un paquete PyInstaller

Existe una gran posibilidad de que su primer intento de usar PyInstaller para empaquetar una aplicación no sea completamente exitoso.

Para comprobar si su paquete PyInstaller funciona, navegue hasta el directorio que contiene el ejecutable incluido y ejecute el .exearchivo allí desde la línea de comandos. Si no se ejecuta, los errores que verá impresos en la línea de comando deberían proporcionar una pista de lo que está mal.

La razón más común por la que falla un paquete PyInstaller es que PyInstaller no pudo empaquetar un archivo requerido. Los archivos que faltan se dividen en algunas categorías:

  • Importaciones ocultas o faltantes : a veces, PyInstaller no puede detectar la importación de un paquete o biblioteca, normalmente porque se importa dinámicamente. El paquete o biblioteca deberá especificarse manualmente.
  • Faltan archivos independientes : si el programa depende de archivos de datos externos que deben incluirse con el programa, PyInstaller no tiene forma de saberlo. Deberá incluir manualmente los archivos.
  • Binarios faltantes : aquí nuevamente, si su programa depende de un binario externo como un .DLL que PyInstaller no puede detectar, deberá incluirlo manualmente.

La buena noticia es que PyInstaller proporciona una forma sencilla de solucionar los problemas anteriores. El .specarchivo creado por PyInstaller incluye campos que podemos completar para proporcionar los detalles que PyInstaller pasó por alto.

Abra el .specarchivo en un editor de texto y busque la definición del Analysisobjeto. Varios de los parámetros que se pasan Analysisson listas en blanco, pero se pueden editar para especificar los detalles que faltan:

  • hiddenimportspara importaciones ocultas o faltantes : agregue a esta lista una o más cadenas con los nombres de las bibliotecas que desea incluir con su aplicación. Si quisiera agregar pandasy bokeh, por ejemplo, lo especificaría como  ['pandas','bokeh']. Tenga en cuenta que las bibliotecas en cuestión deben instalarse en la misma instancia de Python donde está ejecutando PyInstaller.
  • dataspara archivos independientes que faltan : agregue aquí una o más especificaciones para los archivos en su árbol de proyecto que desea incluir con su proyecto. Cada archivo debe pasarse como una tupla que indica la ruta relativa al archivo en el directorio de su proyecto y la ruta relativa dentro del directorio de distribución donde desea colocar el archivo. Por ejemplo, si tiene un archivo ./models/mainmodel.datque desea incluir con su aplicación y desea colocarlo en un subdirectorio coincidente en su directorio de distribución, lo usaría ('./models/mainmodel.dat','./models')como una entrada en la hiddenimportslista. Tenga en cuenta que puede utilizar globcomodines -style para especificar más de un archivo.
  • binariespara binarios independientes que faltan : al igual que con datas, puede usar binariespara pasar una lista de tuplas que especifican las ubicaciones de los binarios en el árbol del proyecto y sus destinos en el directorio de distribución. Nuevamente, puede usar globcomodines de estilo.

Tenga en cuenta que cualquiera de las listas pasadas Analysisse puede generar mediante programación anteriormente en el .specarchivo. Después de todo, el .specarchivo es solo una secuencia de comandos de Python con otro nombre.

Después de realizar cambios en el .specarchivo, vuelva a ejecutar PyInstaller para reconstruir el paquete. Sin embargo, a partir de ahora, asegúrese de pasar el .specarchivo modificado como parámetro (p pyinstaller myapp.spec. Ej .). Pruebe el ejecutable como antes. Si algo aún está roto, puede volver a editar el .specarchivo y repetir el proceso hasta que todo funcione.

Finalmente, cuando esté satisfecho de que todo funciona según lo previsto, es posible que desee editar el  .specarchivo para evitar que su aplicación empaquetada presente una ventana de línea de comandos cuando se inicie. En la EXEconfiguración del objeto en el .specarchivo, establezca  console=False. Suprimir la consola es útil si su aplicación tiene una GUI y no desea que una ventana de línea de comandos falsa lleve a los usuarios por mal camino. Por supuesto, no cambie esta configuración si su aplicación requiere una línea de comando.

Refinando un paquete PyInstaller

Una vez que tenga su aplicación empaquetada con PyInstaller y funcionando correctamente, lo siguiente que probablemente querrá hacer es reducirla un poco. Los paquetes de PyInstaller no son conocidos por ser esbeltos.

Debido a que Python es un lenguaje dinámico, es difícil predecir qué necesitará un programa determinado en tiempo de ejecución. Por esa razón, cuando PyInstaller detecta la importación de un paquete, incluye todo en ese paquete, ya sea que su programa lo use o no en tiempo de ejecución. 

Estas son las buenas noticias. PyInstaller incluye un mecanismo para excluir de forma selectiva paquetes completos o espacios de nombres individuales dentro de los paquetes. Por ejemplo, digamos que su programa importa el paquete foo, que incluye foo.bary foo.bip. Si sabe con certeza que su programa solo usa lógica foo.bar, puede excluir foo.bip y ahorrar espacio de manera segura .

Para hacer esto, usa el excludesparámetro pasado al Analysisobjeto en el .specarchivo. Puede pasar una lista de nombres (módulos de nivel superior o espacios de nombres con puntos) para excluirlos de su paquete. Por ejemplo, para excluir foo.bip, simplemente debe especificar  ['foo.bip'].

Una exclusión común que puede hacer es tkinterla biblioteca Python para crear interfaces de usuario gráficas multiplataforma simples. De forma predeterminada,  tkintery todos sus archivos de soporte están empaquetados con un proyecto PyInstaller. Si no lo está utilizando tkinteren su proyecto, puede excluirlo agregando 'tkinter'a la excludeslista. Omitir tkinterreducirá el tamaño del paquete en alrededor de 7 MB.

Otra exclusión común son las suites de prueba. Si un paquete que importa su programa tiene un conjunto de pruebas, el conjunto de pruebas podría terminar incluyéndose en su paquete PyInstaller. A menos que realmente ejecute el conjunto de pruebas en su programa implementado, puede excluirlo de manera segura.

Tenga en cuenta que los paquetes creados con exclusiones deben probarse a fondo antes de ser utilizados. Si termina excluyendo la funcionalidad que se usa en algún escenario futuro que no anticipó, su aplicación se romperá.

Consejos de PyInstaller

  • Cree su paquete PyInstaller en el sistema operativo en el que planea implementar.  PyInstaller no admite compilaciones multiplataforma. Si necesita implementar su aplicación Python independiente en sistemas MacOS, Linux y Windows, deberá instalar PyInstaller y crear versiones independientes de la aplicación en cada uno de estos sistemas operativos. 
  • Cree su paquete PyInstaller a medida que desarrolla su aplicación.  Tan pronto como sepa que implementará su proyecto con PyInstaller, cree su .specarchivo y comience a refinar el paquete PyInstaller en paralelo con el desarrollo de su aplicación. De esta manera, puede agregar exclusiones o inclusiones sobre la marcha y probar la forma en que se implementan las nuevas funciones con la aplicación a medida que las escribe.
  • No use el --onefilemodo de PyInstaller  .  PyInstaller incluye un conmutador de línea de comandos --onefile, que empaqueta toda su aplicación en un solo ejecutable autoextraíble. Parece una gran idea: ¡solo tiene que entregar un archivo! - pero tiene algunas dificultades. Siempre que ejecute la aplicación, primero debe descomprimir todos los archivos dentro del ejecutable en un directorio temporal. Si la aplicación es grande (200 MB, por ejemplo), desembalar puede significar un retraso de varios segundos. En su lugar, use el modo de directorio único predeterminado y simplemente empaque todo como un .ziparchivo.
  • Cree un instalador para su aplicación PyInstaller.  Si desea alguna forma de implementar su aplicación que no sea un archivo .zip, considere usar una utilidad de instalación como el sistema de instalación de código abierto Nullsoft Scriptable. Agrega muy poca sobrecarga al tamaño del entregable y le permite configurar muchos aspectos del proceso de instalación, como crear accesos directos a su ejecutable.
  • No espere aceleraciones.  PyInstaller es un  sistema de empaquetado , no un  compilador  ni un  optimizador . El código empaquetado con PyInstaller no se ejecuta más rápido que cuando se ejecuta en el sistema original. Si desea acelerar el código Python, use una biblioteca acelerada en C adecuada para la tarea o un proyecto como Cython.

Cómo hacer más con Python

  • Tutorial de Cython: Cómo acelerar Python
  • Cómo instalar Python de forma inteligente
  • Mejor gestión de proyectos de Python con Poetry
  • Virtualenv y venv: explicación de los entornos virtuales de Python
  • Python virtualenv y venv qué hacer y qué no hacer
  • Explicación de subprocesos y subprocesos de Python
  • Cómo usar el depurador de Python
  • Cómo usar timeit para perfilar el código Python
  • Cómo usar cProfile para perfilar el código Python
  • Comience con async en Python
  • Cómo usar asyncio en Python
  • Cómo convertir Python a JavaScript (y viceversa)