Объединение файлов данных с помощью PyInstaller (--onefile) - PullRequest
83 голосов
/ 06 октября 2011

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

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

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

Это то, что я добавил в .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')]     

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

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

Ответы [ 8 ]

116 голосов
/ 09 декабря 2012

Более новые версии 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)
49 голосов
/ 06 октября 2011

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"
16 голосов
/ 04 июня 2017

Все остальные ответы используют текущий рабочий каталог в случае, когда приложение не установлено PyInstalle (т.е. 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)
9 голосов
/ 08 июня 2017

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

  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-makepec file.py , в консоль, чтобы создать файл file.spec.

  3. шаг: откройте файл, файл.spec с помощью 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.spec (в моем случае 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. шаг: сохранить и выйти из файла пути.Перейдите в вашу папку, в которой есть файл спецификации и py.Снова откройте окно консоли и введите следующую команду:

        pyinstaller your_file.spec
    

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

6 голосов
/ 06 апреля 2016

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

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

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

2 голосов
/ 17 августа 2015

Небольшое изменение принятого ответа.

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 голосов
/ 26 февраля 2019

Самая распространенная жалоба / вопрос, который я видел в PyInstaller, это «мой код не может найти файл данных, который я определенно включил в комплект, где он находится?», И непросто увидеть, что /где ваш код ищет, потому что извлеченный код находится во временном местоположении и удаляется при выходе.Добавьте этот бит кода в см. что включено в ваш файл и где оно находится, используя @Jonathon Reinhart's resource_path()

for root, dirs, files in os.walk(resource_path("")):
    print(root)
    for file in files:
        print( "  ",file)
0 голосов
/ 18 декабря 2016

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

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

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

После сбоя не нажимайте кнопку OK. Если вы работаете в Windows, выможно использовать Search Everything .Найдите один из ваших файлов (например, 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)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...