通过pyInstaller生成的Python EXE文件中确定应用程序路径

122

我有一个应用程序,它只存在于一个.py文件中。我已经成功地将pyInstaller打包成Windows的可执行文件。问题是,该应用程序需要一个.cfg文件,该文件始终与应用程序直接放在同一目录下。

通常,我使用以下代码构建路径:

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

然而,当使用由pyInstaller生成的EXE文件调用sys.path时,它似乎是空的。当您运行Python交互命令行并尝试获取sys.path[0]时,也会出现相同的行为。

有没有更具体的方法来获取当前运行应用程序的路径,以便我可以找到与之相关的文件?


可能是如何在py2exe中获取可执行文件的当前目录?的重复问题--这个当前的问题比另一个问题更旧,但另一个问题有更多的答案并且更详细记录。 - oHo
12
这两者有很多共同点,但是py2exe != pyinstaller。 - demux
9个回答

177

我找到了解决方案。你需要检查应用程序是作为一个脚本还是作为一个冻结的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)

18
FYI: 对于更新的PyInstaller版本(3.0+),请参考@normanius的回答。 (翻译:供您参考:针对更新的PyInstaller版本(3.0+),请查看@normanius的回答。) - user3834473
3
应该只有else:而不是elif __file__:吧? - Jakub Bláha
1
对于喜欢使用pathlib的用户:bundle_dir = Path(sys.executable).parent另外,您可能想检查一下hasattr(sys, '_MEIPASS')。(应该为True) - Harper
2
这是唯一一个真正可行、干净且不使用sys._MEIPASS的解决方案。 - Boštjan Mejak
还请参考@Max Tet对normanius答案的评论。 - eri0o

104
根据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工作方式的进一步信息。


1
可以确认,在Windows 10和Windows 7上,使用PyInstaller 3.2和Python 3.4可以正常工作 - 其他答案已经不再适用。 - user3834473
1
只是补充一下,根据文档,这适用于单文件和单文件夹分发。 - mac
57
在一个文件的可执行程序中,使用sys._MEIPASS无法正常工作。根据文档说明: “对于一个文件夹捆绑包,这是指该文件夹的路径,无论用户将其放在何处。对于一个单文件捆绑包,这是指引导加载程序所创建的_MEIxxxxxx临时文件夹的路径。” 对于一个文件的可执行程序,请使用sys.executable - Max Tet
15
对于一个 --onefile 可执行文件,应用程序路径由 application_path = os.path.dirname(sys.executable) 给出。 - masher
好的。现在我可以在生产中的其他文件夹中拥有sqlite3,而在“发布构建(exe)”中则是另一个文件夹。 - Chris P
1
刚刚测试了pyinstaller 5.0版本,效果非常好。这是唯一对我有效的答案。另外,使用__file__获取绝对路径是我之前遗漏的关键点。 - Dhara Vihol

12

我把代码缩短了一点。

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


8

我很惊讶没有人提到getattr()具有内置的默认参数,如果属性不存在,则返回该参数。使用pathlib可以使代码更易读。无论代码是否与PyInstaller捆绑,此代码都有效。

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

最后一行不应该是config_path = bundle_dir + '/myapp.cfg'吗?否则回答很好。 - Edward Spencer
@EdwardSpencer,不对,pathlib.Path对象允许您使用/运算符来构建路径。 - maldata

7

__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>

我已经尝试了上述所有方法,使用 PyInstaller 创建的可执行文件只有这个方法有效。谢谢。 - opperman.eric
我也尝试了所有其他的解决方案,这是唯一一个对我有效的答案。非常感谢你!!! - Paul D.

4
os.path.dirname(sys.argv[0])

这对我来说没问题。

7
只有在调用可执行文件时使用绝对路径,才能使其起作用。如果只使用exe文件名,并通过PATH找到它,则返回一个空路径。 - Ber

3

这里有很多答案,但我发现下面的解决方案在大多数情况下都有效:

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

0

我的情况是使用一个运行由 pyinstaller 构建的可执行文件 .exe 的服务。我使用 os.path.realpath(sys.executable) 来获取当前执行文件的绝对路径。

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

0
这是我处理 pyinstaller 的相对路径的方式,如果我旁边有一个我想要使用的 .exe 文件夹:
import os
import pathlib

os.environ['base_path'] = os.path.dirname(sys.argv[0])
files_to_process = glob.glob( str(pathlib.Path(os.environ.get('base_path')) / 'to_process/*.pdf') )

无论您是作为脚本还是可执行文件运行都可以。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接