Объединение файлов данных с помощью PyInstaller (--onefile)
Я пытаюсь создать однофазный EXE с PyInstaller, который должен включать изображение и значок. Я не могу за свою жизнь заставить его работать с --onefile
.
Если я выполняю --onedir
, все работает очень хорошо.
Когда я использую --onefile
, он не может найти указанные дополнительные файлы (при запуске скомпилированного EXE). Он находит DLL и все остальное прекрасным, а не два изображения.
Я посмотрел в temp-dir, сгенерированный при запуске EXE (например, \Temp\_MEI95642\
), и файлы действительно там. Когда я бросаю EXE в этот каталог temp, он находит их. Очень сложно.
Это то, что я добавил в файл .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.
Ответы
Ответ 1
Новые версии PyInstaller больше не устанавливают переменную env
, поэтому Shish отлично answer не будет работать. Теперь путь устанавливается как 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)
Ответ 2
pyinstaller распаковывает ваши данные во временную папку и сохраняет этот путь каталога в переменной среды _MEIPASS2
. Чтобы получить директорию _MEIPASS2
в упакованном режиме и использовать локальный каталог в режиме распаковки (разработки), я использую это:
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"
Ответ 3
Все остальные ответы используют текущий рабочий каталог в случае, когда приложение не установлено 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)
Ответ 4
Возможно, я пропустил шаг или сделал что-то не так, но методы, которые выше, не связывают файлы данных с PyInstaller в один файл exe. Позвольте мне поделиться тем, что я сделал.
-
Шаг: напишите один из вышеуказанных методов в файл 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)
-
Шаг: напишите, pyi-makepec file.py, на консоль, чтобы создать файл file.spec.
-
Шаг: Откройте файл file.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')
-
: Я выполнил следующие шаги, а затем сохранил файл spec. Наконец, откройте консоль и напишите pyinstaller file.spec (в моем случае, файл = конвертер-GUI).
Заключение: в папке dist еще несколько файлов.
Примечание. Я использую Python 3.5.
EDIT: Наконец, он работает с методом Джонатана Рейнхарта.
-
Шаг: добавьте приведенные ниже коды в файл 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)
-
Шаг: вызовите вышеуказанную функцию с добавлением пути к вашему файлу:
image_path = resource_path("Converter-GUI.ico")
-
Шаг: напишите вышеперечисленную переменную, которая вызывает функцию, где ваши коды должны иметь путь. В моем случае это:
self.window.iconbitmap(image_path)
-
: Откройте консоль в той же папке вашего файла python, напишите коды, как показано ниже:
pyinstaller --onefile your_file.py
- : Откройте файл .spec файла python и добавьте массив a.datas и добавьте значок в класс exe, который был указан выше перед редактированием на 3-м шаге.
-
: Сохранить и выйти из файла пути. Перейдите в папку, в которую входят файл spec и py. Откройте снова окно консоли и введите следующую команду:
pyinstaller your_file.spec
После 6 шага ваш один файл готов к использованию.
Ответ 5
Вместо того, чтобы переписывать весь мой код пути, как было предложено, я сменил рабочий каталог:
if getattr(sys, 'frozen', False):
os.chdir(sys._MEIPASS)
Просто добавьте эти две строки в начале кода, вы можете оставить остальные как есть.
Ответ 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)
Ответ 7
Я обнаружил, что существующие ответы запутываются, и уделило много времени, чтобы решить, где проблема. Вот компиляция всего, что я нашел.
Когда я запускаю свое приложение, я получаю сообщение об ошибке Failed to execute script foo
(если foo.py
является основным файлом). Чтобы устранить эту проблему, не запускайте PyInstaller с помощью --noconsole
(или отредактируйте main.spec
, чтобы изменить console=False
= > console=True
). При этом запустите исполняемый файл из командной строки, и вы увидите сбой.
Первое, что нужно проверить, - это правильно упаковать ваши лишние файлы. Вы должны добавить кортежи, например ('x', 'x')
, если вы хотите включить папку x
.
После сбоя не нажимайте OK. Если вы находитесь в 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)
Ответ 8
Самая распространенная жалоба/вопрос, который я видел в PyInstaller: "Мой код не может найти файл данных, который я определенно включил в комплект, где он находится?", И непросто увидеть, что/где ваш код выполняет поиск, потому что извлеченный код находится во временном местоположении и удаляется при выходе. Добавьте этот фрагмент кода, чтобы увидеть, что входит в ваш файл и где он находится, используя @Jonathon Reinhart resource_path()
for root, dirs, files in os.walk(resource_path("")):
print(root)
for file in files:
print( " ",file)