Связывание файлов данных с PyInstaller (--onefile)


Я пытаюсь построить один файл EXE с PyInstaller, который должен включать изображение и значок. Я не могу для жизни меня заставить его работать с --onefile.

если я это сделаю --onedir это работает, все работает очень хорошо. Когда я использую --onefile, он не может найти указанные дополнительные файлы (при запуске скомпилированного EXE). Он находит библиотеки DLL и все остальное нормально, только не два изображения.

Я посмотрел в temp-dir, созданный при запуске EXE (Temp_MEI95642 например) и файлы действительно там. Когда я бросаю EXE в этот temp-каталог, он находит их. Очень озадачивает.

это то, что я добавил к .spec file

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

Я должен добавить, что я пытался не помещать их в подпапки, а также, не имеет значения.

Edit:отмечен более новый ответ как правильный из-за обновления PyInstaller.

7 65

7 ответов:

новые версии PyInstaller не устанавливают env переменная больше, так что шиш отлично ответ не будет работать. Теперь путь устанавливается как 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 распаковывает ваши данные во временную папку и сохраняет этот путь к каталогу в _MEIPASS2 переменные среды. Чтобы получить _MEIPASS2 dir в упакованном режиме и использовать локальный каталог в режиме распаковки (разработки), я использую это:

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

выход:

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

все остальные ответы используйте текущий рабочий каталог в случае, когда приложение не установлено PyInstalled (т. е. sys._MEIPASS не установлен). Это неправильно, так как это мешает вам запускать приложение из каталога, отличного от того, где находится ваш скрипт.

лучшее решение:

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)

вместо того, чтобы переписать весь мой код пути, как было предложено, я изменил рабочий каталог:

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

просто добавьте эти две строки в самом начале вашего кода, Вы можете оставить остальное как есть.

возможно, я пропустил шаг или сделал что-то неправильно, но методы, которые указаны выше, не связывали файлы данных с PyInstaller в один exe-файл. Позвольте мне поделиться шагами, что я сделал.

  1. шаг: напишите один из вышеуказанных методов в ваш файл py с импортированием модулей sys и 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. шаг: написать, pyi-makespec file.py, в консоль, чтобы создать файл.спекуляция файл.

  3. шаг: открыть, файл.спецификаций с помощью Notepad++, чтобы добавить файлы данных, как показано ниже:

    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. шаг: я выполнил описанные выше шаги, а затем сохранил файл спецификации. Наконец открыл консоль и пишу,файл pyinstaller.спецификации (в моем случае file=Converter-GUI).

вывод: в папке dist все еще есть более одного файла.

Примечание: я использую Python 3.5.

изменить: Наконец-то он работает с метод Джонатана Рейнхарта.

  1. шаг: добавьте следующие коды в свой файл python с импортом sys и 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. шаг: вызовите вышеуказанную функцию с добавлением пути к вашему файлу:

    image_path = resource_path("Converter-GUI.ico")
    
  3. шаг: напишите вышеуказанную переменную, которая вызывает функцию туда, где вашим кодам нужен путь. В моем случае это:

        self.window.iconbitmap(image_path)
    
  4. шаг: открыть консоль в том же каталоге файл python, писать коды, как показано ниже:

        pyinstaller --onefile your_file.py
    
  5. шаг: открыть .spec-файл файла python и добавьте массив a. datas и добавьте значок в класс exe, который был указан выше перед редактированием на 3-м шаге.
  6. шаг: сохраните и выйдите из файла пути. Перейдите в папку, в которую входит файл spec и py. Снова откройте окно консоли и введите следующее команда:

        pyinstaller your_file.spec
    

после 6. шаг ваш один файл готов к использованию.

небольшое изменение, которое не принято отвечать.

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)

я нашел существующие ответы запутанными, и потребовалось много времени, чтобы решить, где проблема. Вот подборка всего, что я нашел.

когда я запускаю мое приложение, я получаю сообщение об ошибке Failed to execute script foo (Если foo.py - основной файл). Чтобы устранить эту проблему, не запускайте PyInstaller с --noconsole (или редактировать main.spec изменить console=False=>console=True). При этом запустите исполняемый файл из командной строки, и вы увидите сбой.

первое, что нужно проверить-это упаковка ваши дополнительные файлы правильно. Вы должны добавить кортежи как ('x', 'x') если вы хотите в папку x для включения.

после того, как он падает,не нажимайте кнопку ОК. если вы находитесь на Windows, вы можете использовать Искать Все. Найдите один из ваших файлов (например. sword.png). Вы должны найти временный путь, где он распаковал файлы (например. C:\Users\ashes999\AppData\Local\Temp\_MEI157682\images\sword.png). Вы можете просмотреть этот каталог и убедитесь, что он включен и все. Если вы не можете найти его таким образом, что-то искать как main.exe.manifest (Windows) или python35.dll (если вы используете Python 3.5).

если установщик включает все, следующая вероятная проблема - это файловый ввод / вывод: ваш код Python ищет файлы в каталоге исполняемого файла, а не в каталоге temp.

чтобы исправить это, любой из ответов на этот вопрос работает. Лично я нашел смесь из них всех для работы: изменить каталог условно первым делом в главном файле точки входа, а все остальное работает как есть:

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