147 votes

Regroupement des fichiers de données avec PyInstaller (--onefile)

J'essaie de construire un EXE d'un seul fichier avec PyInstaller qui doit inclure une image et une icône. Je ne parviens pas à le faire fonctionner avec --onefile .

Si je le fais --onedir tout fonctionne très bien. Lorsque j'utilise --onefile En revanche, il ne trouve pas les fichiers supplémentaires référencés (lorsqu'il exécute l'EXE compilé). Il trouve les DLL et tout le reste sans problème, mais pas les deux images.

J'ai regardé dans le répertoire temporaire généré lors de l'exécution de l'EXE ( \Temp\_MEI95642\ par exemple) et les fichiers s'y trouvent effectivement. Lorsque je dépose l'EXE dans ce répertoire temporaire, il les trouve. Cela me laisse très perplexe.

C'est ce que j'ai ajouté à la .spec fichier

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

Je dois ajouter que j'ai essayé de ne pas les mettre dans des sous-dossiers également, cela n'a pas fait de différence.

Editar: Marqué une réponse plus récente comme correcte en raison de la mise à jour de PyInstaller.

11 votes

Merci beaucoup ! la ligne ici ( a.datas += ... ) m'a vraiment aidé à l'instant. La documentation de pyinstaller parle de l'utilisation de COLLECT mais cela ne permet pas de mettre les fichiers dans le binaire lorsque l'on utilise --onefile

0 votes

@IgorSerebryany : Je suis d'accord ! Je viens d'avoir exactement le même problème.

0 votes

Mon .exe se plante lorsque je clique sur la barre de menu si j'ai utilisé

8voto

Bobsleigh Points 153

Au lieu de réécrire tout mon code de chemin comme suggéré, j'ai changé le répertoire de travail :

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

Ajoutez simplement ces deux lignes au début de votre code, vous pouvez laisser le reste tel quel.

2 votes

Non. Les bonnes applications doivent rarement changer le répertoire de travail. Il existe de meilleures méthodes.

5voto

Jtonna Points 65

En utilisant l'excellente réponse de Max y Ce poste sur l'ajout de fichiers de données supplémentaires tels que des images ou du son & mes propres recherches/tests, j'ai trouvé ce que je crois être la manière la plus simple d'ajouter de tels fichiers.

Si vous souhaitez voir un exemple concret, mon référentiel est le suivant aquí sur GitHub.

Note : ceci est pour la compilation en utilisant le --onefile o -F avec pyinstaller.

Mon environnement est le suivant.


Résoudre le problème en 2 étapes

Pour résoudre ce problème, nous devons indiquer spécifiquement à Pyinstaller que nous avons des fichiers supplémentaires qui doivent être "regroupés" avec l'application.

Nous devons également être en utilisant un chemin "relatif". afin que l'application puisse fonctionner correctement lorsqu'elle est exécutée comme un script Python ou un Frozen EXE.

Ceci étant dit, nous avons besoin d'une fonction qui nous permette d'avoir des chemins relatifs. En utilisant la fonction Max a posté nous pouvons facilement résoudre le problème du cheminement relatif.

def img_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)

Nous utiliserions la fonction ci-dessus comme ceci afin que l'icône de l'application s'affiche lorsque l'application est exécutée en tant que script OU EXE gelé.

icon_path = img_resource_path("app/img/app_icon.ico")
root.wm_iconbitmap(icon_path)

L'étape suivante consiste à indiquer à Pyinstaller où trouver les fichiers supplémentaires lors de la compilation afin que, lors de l'exécution de l'application, ils soient créés dans le répertoire temporaire.

Nous pouvons résoudre ce problème de deux manières, comme le montre l'exemple ci-dessous. documentation mais je préfère personnellement gérer mon propre fichier .spec, c'est donc ainsi que nous allons procéder.

Tout d'abord, vous devez déjà avoir un fichier .spec. Dans mon cas, j'ai pu créer ce dont j'avais besoin en exécutant pyinstaller avec des args supplémentaires, vous pouvez trouver des args supplémentaires aquí . Pour cette raison, mon fichier de spécifications peut être un peu différent du vôtre, mais je le publie dans son intégralité à titre de référence après avoir expliqué les éléments importants.

fichiers ajoutés est essentiellement une liste contenant des Tuple's, dans mon cas, je ne veux ajouter qu'une SEULE image, mais vous pouvez ajouter plusieurs ico's, png's ou jpg's en utilisant ('app/img/*.ico', 'app/img') Vous pouvez également créer un autre tuple comme suit added_files = [ (), (), ()] pour avoir des importations multiples

La première partie du tuple définit quel fichier ou quel type de du fichier que vous souhaitez ajouter ainsi que l'endroit où les trouver. Pensez-y comme CTRL+C

La deuxième partie du tuple indique à Pyinstaller de créer le chemin 'app/img/' et de placer les fichiers dans ce répertoire RELATIVEMENT au répertoire temporaire créé lorsque vous exécutez le .exe. Pensez-y comme CTRL+V

Sous a = Analysis([main... J'ai mis datas=added_files à l'origine, c'était datas=[] mais nous voulons que la liste des importations soit, eh bien, importée, donc nous passons dans nos importations personnalisées.

Vous n'avez pas besoin de faire cela à moins que vous ne vouliez une icône spécifique pour l'EXE, au bas du fichier spec je dis à Pyinstaller de définir l'icône de mon application pour l'exe avec l'option icon='app\\img\\app_icon.ico' .

added_files = [
    ('app/img/app_icon.ico','app/img/')
]
a = Analysis(['main.py'],
             pathex=['D:\\Github Repos\\Processes-Killer\\Process Killer'],
             binaries=[],
             datas=added_files,
             hiddenimports=[],
             hookspath=[],
             runtime_hooks=[],
             excludes=[],
             win_no_prefer_redirects=False,
             win_private_assemblies=False,
             cipher=block_cipher,
             noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
             cipher=block_cipher)
exe = EXE(pyz,
          a.scripts,
          a.binaries,
          a.zipfiles,
          a.datas,
          [],
          name='Process Killer',
          debug=False,
          bootloader_ignore_signals=False,
          strip=False,
          upx=True,
          upx_exclude=[],
          runtime_tmpdir=None,
          console=True , uac_admin=True, icon='app\\img\\app_icon.ico')

Compilation en EXE

Je suis très paresseux ; je n'aime pas taper les choses plus que nécessaire. J'ai créé un fichier .bat que je peux simplement cliquer. Vous n'avez pas besoin de le faire, ce code fonctionnera très bien dans une invite de commande sans cela.

Puisque le fichier .spec contient tous les paramètres de compilation et les args (c'est-à-dire les options), il suffit de donner ce fichier .spec à Pyinstaller.

pyinstaller.exe "Process Killer.spec"

3voto

Krishna Balan Points 256

Légère modification de la réponse acceptée.

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)

0 votes

Utiliser le répertoire de travail actuel dans le cas où _MEIPASS n'est pas défini est une erreur. Voir ma réponse .

1voto

barny Points 16

La plainte/question la plus courante que j'ai vue concernant PyInstaller est "mon code ne peut pas trouver un fichier de données que j'ai définitivement inclus dans le bundle, où est-il ?", et il n'est pas facile de voir ce que/où votre code cherche parce que le code extrait est dans un emplacement temporaire et est supprimé quand il se termine. Ajoutez ce bout de code à voir ce qui est inclus dans votre onefile et où il se trouve, en utilisant la méthode de @Jonathon Reinhart resource_path()

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

1voto

tsahmatsis Points 31

Une autre solution consiste à créer un hook d'exécution qui copiera (ou déplacera) vos données (fichiers/dossiers) dans le répertoire où l'exécutable est stocké. Le hook est un simple fichier python qui peut presque tout faire, juste avant l'exécution de votre application. Pour le définir, vous devez utiliser la fonction --runtime-hook=my_hook.py de pyinstaller. Ainsi, dans le cas où vos données sont un images vous devez exécuter la commande :

pyinstaller.py --onefile -F --add-data=images;images --runtime-hook=cp_images_hook.py main.py

Le cp_images_hook.py pourrait être quelque chose comme ceci :

import sys
import os
import shutil

path = getattr(sys, '_MEIPASS', os.getcwd())

full_path = path+"\\images"
try:
    shutil.move(full_path, ".\\images")
except:
    print("Cannot create 'images' folder. Already exists.")

Avant chaque exécution, le images est déplacé dans le répertoire courant (depuis le dossier _MEIPASS), de sorte que l'exécutable y aura toujours accès. De cette façon, il n'est pas nécessaire de modifier le code de votre projet.

Deuxième solution

Vous pouvez tirer parti du mécanisme de crochet d'exécution et changer le répertoire courant, ce qui n'est pas une bonne pratique selon certains développeurs, mais cela fonctionne bien.

Le code d'accrochage se trouve ci-dessous :

import sys
import os

path = getattr(sys, '_MEIPASS', os.getcwd())   
os.chdir(path)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X