Определение пути к приложению в Python EXE, созданном pyInstaller

У меня есть приложение, которое находится в одном файле .py. Мне удалось заставить pyInstaller успешно связать его с EXE для Windows. Проблема в том, что приложению требуется файл .cfg, который всегда находится непосредственно рядом с приложением в том же каталоге.

Обычно я строю путь, используя следующий код:

import os
config_name = 'myapp.cfg'
config_path = os.path.join(sys.path[0], config_name)

Однако кажется, что sys.path пуст, когда он вызывается из EXE, созданного pyInstaller. Такое же поведение происходит, когда вы запускаете интерактивную командную строку python и пытаетесь получить sys.path [0].

Есть ли более конкретный способ получить путь к текущему запущенному приложению, чтобы я мог найти файлы, относящиеся к нему?

возможный дубликат как я могу получить текущий каталог исполняемого файла в py2exe? - этот текущий вопрос старше, но на другой вопрос есть больше ответов и он более документирован.

oHo 12.11.2013 19:07

У них много общего, но py2exe! = Pyinstaller

demux 26.10.2015 03:18
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
69
2
59 881
7
Перейти к ответу Данный вопрос помечен как решенный

Ответы 7

Ответ принят как подходящий

Я нашел решение. Вам нужно проверить, запущено ли приложение как скрипт или как замороженный exe:

import os
import sys

config_name = 'myapp.cfg'

# determine if application is a script file or frozen exe
if getattr(sys, 'frozen', False):
    application_path = os.path.dirname(sys.executable)
elif __file__:
    application_path = os.path.dirname(__file__)

config_path = os.path.join(application_path, config_name)

К вашему сведению: для более новых выпусков PyInstaller (3.0+) см. Ответ @normanius.

user3834473 05.06.2017 22:33

Разве не должен быть только else: вместо elif __file__:?

Jakub Bláha 13.12.2017 17:49

Для пользователей, которым нравится также использовать pathlib: bundle_dir = Path(sys.executable).parent, вы можете проверить hasattr(sys, '_MEIPASS'). (должно быть верно)

Harper 20.01.2021 23:19
os.path.dirname(sys.argv[0])

Это подходит для меня.

Это работает, только если вы используете абсолютный путь к исполняемому файлу при его вызове. Использование только имени exe и обнаружение через PATH возвращает пустой путь.

Ber 18.07.2013 16:59

Согласно документация PyInstaller, предлагаемый метод восстановления пути к приложению выглядит следующим образом:

#!/usr/bin/python3
import sys, os
if getattr(sys, 'frozen', False):
    # If the application is run as a bundle, the PyInstaller bootloader
    # extends the sys module by a flag frozen=True and sets the app 
    # path into variable _MEIPASS'.
    application_path = sys._MEIPASS
else:
    application_path = os.path.dirname(os.path.abspath(__file__))

Протестировано для PyInstaller v3.2, но определенно работает и для более ранних версий.

Решение Soviut не работает, по крайней мере, в целом для последних версий pyInstaller (обратите внимание, что OP много лет). Например, в MacOS при объединении приложения в пакет из одного файла sys.executable указывает только на расположение встроенного архива, которым является нет - расположение, в котором приложение фактически запускается после того, как загрузчик pyInstaller создал временную среду приложения. Только sys._MEIPASS правильно указывает на это место. Обратитесь к эта страница документа для получения дополнительной информации о том, как работает PyInstaller.

Могу подтвердить, что это работает в Windows 10 и Windows 7 с PyInstaller 3.2 и Python 3.4 - другие ответы больше не работают.

user3834473 05.06.2017 22:31

просто добавив, что в соответствии с документами это работает как для однофайловых, так и для однопапочных дистрибутивов

mac 29.03.2018 15:55

Использование sys._MEIPASS не работает для однофайловых исполняемых файлов. Из документов: For a one-folder bundle, this is the path to that folder, wherever the user may have put it. For a one-file bundle, this is the path to the _MEIxxxxxx temporary folder created by the bootloader . Используйте sys.executable для однофайловых исполняемых файлов.

Max Tet 12.07.2018 13:26

Подтверждаю, работает на macOS

Fabio Magarelli 14.01.2021 13:10

Я немного сократил код.

import os, sys

if getattr(sys, 'frozen', False):
    application_path = os.path.dirname(sys.executable)
    os.chdir(application_path)

logging.debug('CWD: ' + os.getcwd())

Но sys._MEIPASS указал не на тот каталог. Думаю тоже нужен sys._MEIPASS + \app_name

__file__ работает из командной строки с исполняемым файлом Python. Он также дает имя файла сценария без фактического пути в замороженном режиме. Однако в интерактивном режиме выдает ошибку.

Следующее будет работать для всех трех режимов:

import sys,os

config_name = 'myapp.cfg'

if getattr(sys, 'frozen', False):
    application_path = os.path.dirname(sys.executable)
    running_mode = 'Frozen/executable'
else:
    try:
        app_full_path = os.path.realpath(__file__)
        application_path = os.path.dirname(app_full_path)
        running_mode = "Non-interactive (e.g. 'python myapp.py')"
    except NameError:
        application_path = os.getcwd()
        running_mode = 'Interactive'

config_full_path = os.path.join(application_path, config_name)

print('Running mode:', running_mode)
print('  Appliction path  :', application_path)
print('  Config full path :', config_full_path)

Вывод в трех разных режимах:

Running mode: Interactive
  Appliction path  : C:\Projects\MyAppDir
  Config full path : C:\Projects\MyAppDir\myapp.cfg

C:\Projects\MyAppDir>myapp.exe
Running mode: Frozen/executable
  Appliction path  : C:\Program Files\myapp
  Config full path : C:\Program Files\myapp\myapp.cfg

C:\Projects\MyAppDir>python myapp.py
Running mode: Non-interactive (e.g. 'python myapp.py')
  Appliction path  : C:\Projects\MyAppDir
  Config full path : C:\Projects\MyAppDir\myapp.cfg

C:\Projects\MyAppDir>

Здесь много ответов, но я обнаружил, что это решение работает в большинстве ситуаций:

import os
import sys
import os.path as op
try:
    this_file = __file__
except NameError:
    this_file = sys.argv[0]
this_file = op.abspath(this_file)
if getattr(sys, 'frozen', False):
    application_path = getattr(sys, '_MEIPASS', op.dirname(sys.executable))
else:
    application_path = op.dirname(this_file)

Для меня это приводит к пути выполнения в AppData \ Local \ Temp_MEI ... вопрос был о папке, в которой находится EXE-файл.

cslotty 23.03.2020 20:22

Я удивлен, что никто не упомянул, что getattr() имеет встроенный аргумент по умолчанию, который будет возвращен, если атрибут не существует. Это также можно сделать более читаемым с помощью pathlib. Этот код работает независимо от того, включен ли код в PyInstaller.

from pathlib import Path
bundle_dir = Path(getattr(sys, '_MEIPASS', Path.cwd()))
config_path = bundle_dir / 'myapp.cfg'

Другие вопросы по теме