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é

207voto

max Points 496

Les versions plus récentes de PyInstaller ne définissent pas l'attribut env plus de variable, donc l'excellent réponse ne fonctionnera pas. Maintenant, le chemin est défini comme 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)

14 votes

J'utilise pyinstaller 2 avec python 2.7 et je ne dispose pas de _MEIPASS2 dans les envs, mais sys._MEIPASS fonctionne bien, donc +1. Je suggère : path = getattr(sys, '_MEIPASS', os.getcwd())

2 votes

+1. Merci pour cela. J'essaie d'utiliser la variable d'environnement _MEIPASS2 depuis un moment maintenant, de toute évidence j'ai écrit mon code quand c'était encore la variable d'environnement, parce que je me souviens que cela fonctionnait. Tout d'un coup, il s'est arrêté, quand j'ai recompilé récemment.

13 votes

Je vous recommande d'aller voir le AttributeError au lieu de l'option plus générale Exception . J'ai rencontré des problèmes où il me manquait l'importation de sys et où le chemin d'accès était par défaut le suivant os.path.abspath .

59voto

Shish Points 1223

Pyinstaller décompose vos données dans un dossier temporaire, et stocke ce chemin de répertoire dans le fichier _MEIPASS2 variable d'environnement. Pour obtenir le _MEIPASS2 en mode packagé et utiliser le répertoire local en mode non packagé (développement), j'utilise ceci :

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

Sortie :

# 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"

12 votes

Comment, quand et où l'utiliser ?

5 votes

Vous devez utiliser ce script dans le fichier .py que vous essayez de compiler avec PyInstaller. Ne mettez pas ce bout de code dans le fichier .spec, cela ne fonctionnera pas. Accédez à vos fichiers en remplaçant le chemin que vous auriez normalement tapé par resource_path("file_to_be_accessed.mp3") . Attention, vous devez utiliser la réponse de max' pour la version actuelle de PyInstaller.

1 votes

Cette réponse est-elle spécifique à l'utilisation de --one-file option ?

47voto

Jonathon Reinhart Points 40535

Toutes les autres réponses utilisent le le répertoire de travail actuel dans le cas où l'application n'est pas PyInstalled (i.e. sys._MEIPASS n'est pas réglé). C'est une erreur, car cela vous empêche de lancer votre application à partir d'un autre répertoire que celui où se trouve votre script.

Une meilleure solution :

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)

0 votes

Merci Jon, votre fonction m'a aidé aujourd'hui à résoudre un problème. J'ai juste une question. Pourquoi certains écrivent _MEIPASS2 au lieu de _MEIPASS ? Quand j'ai essayé d'ajouter le 2 avant, ça n'a pas marché.

1 votes

Je pense os.env['_MEIPASS2'] (en utilisant l'environnement du système d'exploitation) était une ancienne méthode pour obtenir le répertoire. AFAIK sys._MEIPASS est la seule méthode correcte maintenant.

1 votes

Bonjour, votre solution fonctionne bien lorsque resource_path() est dans le script principal. Y a-t-il un moyen de le faire fonctionner lorsqu'il est écrit dans un module à la place ? Pour l'instant, il essaiera d'obtenir des ressources dans le dossier où se trouve le module, et non le principal.

22voto

dilde olupbiten Points 364

Peut-être ai-je manqué une étape ou fait quelque chose de mal, mais les méthodes ci-dessus n'ont pas regroupé les fichiers de données avec PyInstaller dans un seul fichier exe. Laissez-moi vous expliquer ce que j'ai fait.

  1. étape:Ecrivez une des méthodes ci-dessus dans votre fichier py en important les modules sys et os. J'ai essayé les deux. La dernière est la suivante :

    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. étape : Écrire, pyi-makespec file.py à la console, pour créer un fichier .spec.

  3. étape : Ouvrez, file.spec avec Notepad++ pour ajouter les fichiers de données comme ci-dessous :

    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. étape : J'ai suivi les étapes ci-dessus, puis j'ai enregistré le fichier spec. Enfin, j'ai ouvert la console et écrit, fichier pyinstaller.spec (dans mon cas, file=Converter-GUI).

Conclusion : Il y a toujours plus d'un fichier dans le dossier dist.

Note : J'utilise Python 3.5.

EDIT : Finalement, cela fonctionne avec La méthode de Jonathan Reinhart.

  1. étape : Ajoutez les codes ci-dessous à votre fichier python en important sys et 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)
  2. étape : Appelez la fonction ci-dessus en ajoutant le chemin de votre fichier :

    image_path = resource_path("Converter-GUI.ico")
  3. étape : Ecrivez la variable ci-dessus qui appelle la fonction à l'endroit où vos codes ont besoin du chemin. Dans mon cas, c'est :

        self.window.iconbitmap(image_path)
  4. étape : Ouvrez la console dans le même répertoire que votre fichier python, écrivez les codes comme ci-dessous :

        pyinstaller --onefile your_file.py
  5. étape : Ouvrez le fichier .spec du fichier python et ajoutez le tableau a.datas et ajoutez l'icône à la classe exe, qui a été donnée ci-dessus avant l'édition dans la 3ème étape.

  6. étape : Sauvegardez et quittez le fichier de cheminement. Allez dans votre dossier qui contient les fichiers spec et py. Ouvrez à nouveau la fenêtre de la console et tapez la commande ci-dessous :

        pyinstaller your_file.spec

Après l'étape 6, votre fichier unique est prêt à être utilisé.

0 votes

Merci @dilde ! Non pas que le a.datas de l'étape 5 prend des tuples de chaînes de caractères, voir pythonhosted.org/PyInstaller/spec-files.html#addition-de-files-de-données pour référence.

0 votes

Désolé, je ne peux pas comprendre une partie de votre message aussi bien que je le souhaiterais en raison de mon anglais faible. Cette partie est "Pas que l'a.datas...." Voulez-vous écrire "Notez que les datas..." Y a-t-il un problème avec ce que j'ai écrit sur l'ajout de chaînes de caractères à un tableau .datas ?

0 votes

Oups ! Cela devrait être Notez que... Oui, désolé pour la faute de frappe. Tes étapes ont bien fonctionné pour moi :) Je n'ai pas pu trouver cette information dans leur documentation ou ailleurs.

10voto

muyustan Points 1053

Je suis confronté à ce problème depuis longtemps, très long) temps. J'ai cherché dans presque toutes les sources mais les choses ne se mettaient pas en place dans ma tête.

Enfin, je pense avoir trouvé les étapes exactes à suivre, je voulais partager.

Notez que, ma réponse utilise des informations sur les réponses des autres sur cette question.

Comment créer un exécutable autonome d'un projet python.

Supposons que nous ayons un dossier de projet et que l'arbre des fichiers soit le suivant :

project_folder/
    main.py
    xxx.py # another module
    yyy.py # another module
    sound/ # directory containing the sound files
    img/ # directory containing the image files
    venv/ # if using a venv

Tout d'abord, disons que vous avez défini vos chemins pour sound/ y img/ dossiers dans des variables sound_dir y img_dir comme suit :

img_dir = os.path.join(os.path.dirname(__file__), "img")
sound_dir = os.path.join(os.path.dirname(__file__), "sound")

Vous devez les modifier, comme suit :

img_dir = resource_path("img")
sound_dir = resource_path("sound")

Où, resource_path() est défini en haut de votre script comme :

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)

Activez l'environnement virtuel si vous utilisez un venv,

Installez pyinstaller si vous ne l'avez pas encore fait, par : pip3 install pyinstaller .

Cours : pyi-makespec --onefile main.py pour créer le fichier spec pour le processus de compilation et de construction.

Cela changera la hiérarchie des fichiers en :

project_folder/
    main.py
    xxx.py # modules
    xxx.py # modules
    sound/ # directory containing the sound files
    img/ # directory containing the image files
    venv/ # if using a venv
    main.spec

Ouvert (avec un édile) main.spec :

En haut, insérer :

added_files = [

("sound", "sound"),
("img", "img")

]

Ensuite, modifiez la ligne de datas=[], a datas=added_files,

Pour le détail des opérations effectuées sur main.spec voir ici.

Exécuter pyinstaller --onefile main.spec

Et c'est tout, vous pouvez courir main en project_folder/dist de n'importe où, sans avoir rien d'autre dans son dossier. Vous ne pouvez distribuer que ce main fichier. C'est maintenant, un véritable autonome .

0 votes

@JonathonReinhart si vous êtes toujours là, je voulais vous informer que j'ai utilisé votre fonction dans ma réponse.

1 votes

Cela fonctionne comme un charme ! Vous m'avez fait gagner beaucoup de temps.

0 votes

Cela signifie qu'un simple code python pour lire un fichier texte ne fonctionnera pas comme exe à moins que nous suivions les étapes ci-dessus ?

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