如何使用Python3在PyInstaller中设置相对路径来创建可移植的.exe版本?
问题描述
我到处都找过了,一个相当琐碎的问题没有得到明确的答复。
我在Windows7上的PyCharm中有一个Python项目,其中包含多个.py
文件(通过"from %package_name%.%script_name% import %class_name%
"连接)和项目内的一个文件夹,其中包含两个简单的文本文件。我已经将PyInstaller 3.6安装到项目的venv
中,并将其用作指向.spec
文件的外部工具。到现在为止还好。.spec
文件如下:
# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
a = Analysis(['C:\Users\%username%\PycharmProjects\%project_folder%\%project_folder%\main.py'],
pathex=['C:\Users\%username%\PycharmProjects\%project_folder%\%project_folder%'],
binaries=[],
datas=[('txt_files\file1.txt', '.'), ('txt_files\file2.txt', '.')],
hiddenimports=[],
hookspath=[],
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False)
a.datas += [
("C:\Users\%username%\PycharmProjects\%project_folder%\%project_folder%\txt_files\file1.txt","txt_files\file1.txt","DATA"),
("C:\Users\%username%\PycharmProjects\%project_folder%\%project_folder%\txt_files\file2.txt","txt_files\file2.txt","DATA"),
]
pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
[],
exclude_binaries=True,
name='%project_name%',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
console=True )
coll = COLLECT(exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
upx_exclude=[],
name='%project_name%')
问题是,如果我在脚本本身中硬编码捆绑的.txt
文件的绝对路径,应用程序会编译并且没有运行时错误。然而,如果我在脚本中使用相对路径,应用程序会编译,但会给出一个运行时错误,即.txt
文件(即file1.txt
)在/build
(或/dist
,我在这里可能是错误的)目录(显然不在那里)中找不到。
当然,硬编码绝对路径是一种糟糕的做法,尤其是在讨论到另一台机器的可移植性时,而且还会使应用程序跨平台。我知道构建过程可能依赖于
sys._MEIPASS
,但我不知道如何在我的上下文中确切地使用它。
在哪个脚本中(
main
、.spec
或其他?)我应该使用sys._MEIPASS
将获得绝对路径的部分放到捆绑文件中吗?这部分代码在Python3.7上应该是什么样子呢?我已经看到了不同的答案(即this one),并且已经尝试过,但似乎都不适用于我的情况。
解决方案
Using--onefile
将所有数据捆绑到.exe
文件中。
当您执行该文件时,这些文件将被"解压缩"到临时文件位置。在Windows上,这通常是C:Users<You>AppDataLocalTempMEIxxx
。
因此,当您开发脚本时,数据文件(本例中的文本文件)将位于
C:\Users\%username%\PycharmProjects\%project_folder%\%project_folder% xt_files
但是当编译应用程序时,它们将被解压到上面提到的临时目录。因此,您需要一种方法来告诉脚本是正在开发,还是已经编译。您可以在此处使用the 'frozen' flag (see the docs here)
我以前使用过的一种方法是创建如下实用程序函数
def resolve_path(path):
if getattr(sys, "frozen", False):
# If the 'frozen' flag is set, we are in bundled-app mode!
resolved_path = os.path.abspath(os.path.join(sys._MEIPASS, path))
else:
# Normal development mode. Use os.getcwd() or __file__ as appropriate in your case...
resolved_path = os.path.abspath(os.path.join(os.getcwd(), path))
return resolved_path
然后,无论何时要在脚本中使用路径,例如访问文本文件,您都可以
with open(resolve_path("txt_files/file1.txt"), "r") as txt:
...
无论您处于哪种模式,它都应该解析正确的路径。
有关.spec文件的说明
- 您不必单独指定所有文本文件。你当然可以,而且你可能有一个很好的理由这样做,这很好。但是你可以
datas=[('txt_files', '.')]
,它将txt_files
目录的内容放在捆绑包的根目录中。但是要小心这一点,因为现在文本文件的路径将是<dev directory> xt_filesfile1.txt
,但在捆绑的应用程序中,它们将是<MEIPASS directory>file1.txt
。您可能希望通过执行以下操作来保持路径的"相对"部分相同
datas=[('txt_files', 'txt_files')]
它将反映您的开发文件夹和捆绑的应用之间的文件结构。
- 还要考虑如果使用规范文件生成,请删除
COLLECT
部分以生成一个捆绑的可执行文件。
相关文章