Agrupar archivos de datos con PyInstaller (–onefile)

Estoy tratando de construir un archivo EXE con PyInstaller que incluya una imagen y un icono. No puedo, por mi vida, hacer que funcione con --onefile .

Si lo hago --onedir funciona todo funciona muy bien. Cuando uso --onefile , no puede encontrar los archivos adicionales a los que se hace referencia (cuando se ejecuta el EXE comstackdo). Encuentra las DLL y todo lo demás bien, pero no las dos imágenes.

He buscado en el directorio temporal generado al ejecutar el EXE ( \Temp\_MEI95642\ por ejemplo) y los archivos están ahí. Cuando coloco el EXE en ese directorio temporal, los encuentra. Muy desconcertante.

Esto es lo que he agregado al archivo .spec

 a.datas += [('images/icon.ico', 'D:\\[workspace]\\App\\src\\images\\icon.ico', 'DATA'), ('images/loaderani.gif','D:\\[workspace]\\App\\src\\images\\loaderani.gif','DATA')] 

Debo agregar que también he intentado no colocarlas en subcarpetas, no hizo una diferencia.

Editar: marcó la respuesta más reciente como correcta debido a la actualización de PyInstaller.

Las versiones más nuevas de PyInstaller ya no establecen la variable env , por lo que la excelente respuesta de Shish no funcionará. Ahora la ruta se establece como sys._MEIPASS :

 def resource_path(relative_path): """ Get absolute path to resource, works for dev and for PyInstaller """ try: # PyInstaller creates a temp folder and stores path in _MEIPASS base_path = sys._MEIPASS except Exception: base_path = os.path.abspath(".") return os.path.join(base_path, relative_path) 

pyinstaller desempaqueta sus datos en una carpeta temporal y almacena esta ruta de directorio en la variable de entorno _MEIPASS2 . Para obtener el directorio _MEIPASS2 en modo empaquetado y usar el directorio local en modo desempaquetado (desarrollo), uso esto:

 def resource_path(relative): return os.path.join( os.environ.get( "_MEIPASS2", os.path.abspath(".") ), relative ) 

Salida:

 # in development >>> resource_path("app_icon.ico") "/home/shish/src/my_app/app_icon.ico" # in production >>> resource_path("app_icon.ico") "/tmp/_MEI34121/app_icon.ico" 

Todas las demás respuestas utilizan el directorio de trabajo actual en el caso de que la aplicación no sea PyInstalled (es decir, sys._MEIPASS no está configurada). Eso es incorrecto, ya que le impide ejecutar su aplicación desde un directorio distinto al directorio donde se encuentra su script.

Una mejor solución:

 import sys import os def resource_path(relative_path): """ Get absolute path to resource, works for dev and for PyInstaller """ base_path = getattr(sys, '_MEIPASS', os.path.dirname(os.path.abspath(__file__))) return os.path.join(base_path, relative_path) 

En lugar de reescribir todo mi código de ruta como se sugirió, cambié el directorio de trabajo:

 if getattr(sys, 'frozen', False): os.chdir(sys._MEIPASS) 

Solo agregue esas dos líneas al comienzo de su código, puede dejar el rest como está.

Quizás perdí un paso o hice algo mal, pero los métodos que se mencionan arriba, no agruparon los archivos de datos con PyInstaller en un archivo exe. Déjame compartir los pasos que he hecho.

  1. paso: escriba uno de los métodos anteriores en su archivo py al importar los módulos sys y os. Probé los dos. El último es:

     def resource_path(relative_path): """ Get absolute path to resource, works for dev and for PyInstaller """ base_path = getattr(sys, '_MEIPASS', os.path.dirname(os.path.abspath(__file__))) return os.path.join(base_path, relative_path) 
  2. paso: Escribe, pyi-makespec file.py , en la consola, para crear un archivo file.spec.

  3. Paso: Abra, file.spec con Notepad ++ para agregar los archivos de datos como a continuación:

     a = Analysis(['C:\\Users\\TCK\\Desktop\\Projeler\\Converter-GUI.py'], pathex=['C:\\Users\\TCK\\Desktop\\Projeler'], binaries=[], datas=[], hiddenimports=[], hookspath=[], runtime_hooks=[], excludes=[], win_no_prefer_redirects=False, win_private_assemblies=False, cipher=block_cipher) #Add the file like the below example a.datas += [('Converter-GUI.ico', 'C:\\Users\\TCK\\Desktop\\Projeler\\Converter-GUI.ico', 'DATA')] pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) exe = EXE(pyz, a.scripts, exclude_binaries=True, name='Converter-GUI', debug=False, strip=False, upx=True, #Turn the console option False if you don't want to see the console while executing the program. console=False, #Add an icon to the program. icon='C:\\Users\\TCK\\Desktop\\Projeler\\Converter-GUI.ico') coll = COLLECT(exe, a.binaries, a.zipfiles, a.datas, strip=False, upx=True, name='Converter-GUI') 
  4. paso: seguí los pasos anteriores, luego guardé el archivo de especificaciones. Por fin abrí la consola y escribí, pyinstaller file.spec (en mi caso, file = Converter-GUI).

Conclusión: todavía hay más de un archivo en la carpeta dist.

Nota: Estoy usando Python 3.5.

EDIT: Finalmente funciona con el método de Jonathan Reinhart.

  1. paso: agrega los siguientes códigos a tu archivo python con la importación de sistemas y sistemas operativos.

     def resource_path(relative_path): """ Get absolute path to resource, works for dev and for PyInstaller """ base_path = getattr(sys, '_MEIPASS', os.path.dirname(os.path.abspath(__file__))) return os.path.join(base_path, relative_path) 
  2. paso: llama a la función anterior agregando la ruta de tu archivo:

     image_path = resource_path("Converter-GUI.ico") 
  3. paso: escriba la variable anterior que llama a la función donde sus códigos necesitan la ruta. En mi caso es:

      self.window.iconbitmap(image_path) 
  4. Paso: Abra la consola en el mismo directorio de su archivo de Python, escriba los códigos como se muestra a continuación:

      pyinstaller --onefile your_file.py 
  5. paso: abra el archivo .spec del archivo python, agregue la matriz a.datas y agregue el icono a la clase exe, que se indicó anteriormente antes de la edición en el paso 3 ‘.
  6. paso: guardar y salir del archivo de ruta. Vaya a su carpeta que incluye el archivo spec y py. Abra de nuevo la ventana de la consola y escriba el siguiente comando:

      pyinstaller your_file.spec 

Después del paso 6. tu archivo está listo para usar.

Modificación leve a la respuesta aceptada.

 def resource_path(relative_path): """ Get absolute path to resource, works for dev and for PyInstaller """ if hasattr(sys, '_MEIPASS'): return os.path.join(sys._MEIPASS, relative_path) return os.path.join(os.path.abspath("."), relative_path) 

Agregue este bit de código para ver lo que está incluido en su archivo único, usando la ruta de resource_path() @Jonathon Reinhart resource_path()

 for root, dirs, files in os.walk(resource_path("")): print(root) for file in files: print( " ",file) 

Encontré confusas las respuestas existentes, y tardé mucho tiempo en averiguar dónde está el problema. Aquí hay una recostackción de todo lo que encontré.

Cuando ejecuto mi aplicación, foo.py un error Failed to execute script foo (si foo.py es el archivo principal). Para solucionar este problema, no ejecute PyInstaller con --noconsole (o edite main.spec para cambiar la console=False => console=True ). Con esto, ejecute el ejecutable desde una línea de comandos, y verá el error.

Lo primero que debe verificar es que está empaquetando sus archivos adicionales correctamente. Debe agregar tuplas como ('x', 'x') si desea que se incluya la carpeta x .

Después de que se bloquea, no haga clic en Aceptar. Si estás en Windows, puedes usar Buscar todo . Busque uno de sus archivos (por ejemplo, sword.png ). Debería encontrar la ruta temporal donde desempaquetó los archivos (por ejemplo, C:\Users\ashes999\AppData\Local\Temp\_MEI157682\images\sword.png ). Puedes navegar por este directorio y asegurarte de que incluya todo. Si no puede encontrarlo de esta manera, busque algo como main.exe.manifest (Windows) o python35.dll (si está usando Python 3.5).

Si el instalador incluye todo, el siguiente problema probable es la E / S de archivos: su código de Python está buscando archivos en el directorio del ejecutable, en lugar del directorio temporal.

Para arreglar eso, cualquiera de las respuestas a esta pregunta funciona. Personalmente, encontré una combinación de todos ellos para trabajar: cambiar el directorio de forma condicional en su archivo de punto de entrada principal, y todo lo demás funciona como está:

if hasattr(sys, '_MEIPASS'): os.chdir(sys._MEIPASS)