Python 使用PyInstaller(--onefile)绑定数据文件
我正在尝试用PyInstaller构建一个包含图像和图标的单文件EXE。我一生都无法让它与Python 使用PyInstaller(--onefile)绑定数据文件,python,pyinstaller,Python,Pyinstaller,我正在尝试用PyInstaller构建一个包含图像和图标的单文件EXE。我一生都无法让它与--onefile一起工作 如果我做了--onedir,一切都很好。 当我使用--onefile时,它无法找到引用的其他文件(在运行编译的EXE时)。它发现DLL和其他一切都很好,只是没有两个图像 我查看了运行EXE时生成的temp dir(例如\temp\\u MEI95642\),文件确实在那里。当我把EXE放到那个临时目录中时,它会找到它们。非常令人困惑 这是我添加到.spec文件中的内容 a.dat
--onefile
一起工作
如果我做了--onedir
,一切都很好。
当我使用--onefile
时,它无法找到引用的其他文件(在运行编译的EXE时)。它发现DLL和其他一切都很好,只是没有两个图像
我查看了运行EXE时生成的temp dir(例如\temp\\u 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更新,将更新的答案标记为正确。PyInstaller将数据解压到临时文件夹中,并将此目录路径存储在环境变量
\u MEIPASS2
中。要在打包模式下获取\u 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"
PyInstaller的较新版本不再设置
env
变量,因此Shish的优秀版本将无法工作。现在路径设置为sys.\u 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)
对已接受答案的轻微修改
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)
为了按照建议重写所有路径代码,我更改了工作目录:
if getattr(sys, 'frozen', False):
os.chdir(sys._MEIPASS)
只需在代码开头添加这两行,其余的就可以保持原样。我发现现有答案令人困惑,花了很长时间才找出问题所在。这是我找到的所有东西的汇编 当我运行我的应用程序时,我得到一个错误
无法执行脚本foo
(如果foo.py
是主文件)。要解决此问题,请不要使用--noconsole运行PyInstaller(或编辑main.spec
以更改console=False
=>console=True
)。这样,从命令行运行可执行文件,您将看到失败
首先要检查的是它是否正确打包了额外的文件。如果希望包含文件夹x
,则应添加类似('x','x')
的元组
崩溃后,不要单击“确定”。如果您使用的是Windows,则可以使用。查找您的一个文件(例如swarm.png
)。您应该找到解压文件的临时路径(例如C:\Users\ashes999\AppData\Local\Temp\\u MEI157682\images\swarm.png
)。您可以浏览此目录并确保它包含所有内容。如果您不能以这种方式找到它,请查找类似于main.exe.manifest
(Windows)或python35.dll
(如果您使用的是Python 3.5)的内容
如果安装程序包含所有内容,那么下一个可能的问题是文件I/O:Python代码在可执行文件的目录中查找文件,而不是临时目录
为了解决这个问题,这个问题的任何答案都是有效的。就我个人而言,我发现它们都能起作用:首先在主入口点文件中有条件地更改目录,其他一切都按原样工作:
如果hasattr(sys,“\u MEIPASS”):
os.chdir(系统)
如果应用程序未安装(即sys.\u 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)
也许我错过了一个步骤或者做了一些错误的事情,但是上面的方法没有将PyInstaller中的数据文件绑定到一个exe文件中。让我分享一下我所做的步骤
步骤:通过导入sys和os模块,将上述方法之一写入py文件。我两个都试过了。最后一点是:
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 makespec file.py写入控制台,以创建file.spec文件
步骤:使用Notepad++打开file.spec,添加如下数据文件:
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')
pyinstaller --onefile your_file.py
步骤:我按照上面的步骤,然后保存了spec文件。最后打开控制台并编写pyinstaller file.spec(在我的例子中,file=Converter GUI)
结论:dist文件夹中仍有多个文件
注意:我使用的是Python 3.5
编辑:最后它与
步骤:通过导入sys和os将以下代码添加到python文件中
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文件的同一目录中打开控制台,编写如下代码:
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')
pyinstaller --onefile your_file.py
步骤:打开python文件的.spec文件,附加a.datas数组,并将图标添加到exe类中,这是在第3步编辑之前给出的
步骤:保存并退出路径文件。转到包含spec和py文件的文件夹。再次打开console窗口并键入以下命令:
pyinstaller your_file.spec
六点以后。步骤:您的一个文件已准备好使用。我所看到的wrt PyInstaller最常见的投诉/问题是“我的代码找不到我明确包含在捆绑包中的数据文件,它在哪里?”,并且很难看到您的代码正在搜索什么/在哪里,因为提取的代码位于临时位置,并且在退出时会被删除。使用@Jonathon Reinhart的resource\u path()
如果您仍试图将文件相对于可执行文件而不是放在临时目录中,则需要自己复制它。我就是这样完成的
在spec文件中添加一个步骤,该步骤将文件系统复制到DISTPATH variabl
img_dir = os.path.join(os.path.dirname(__file__), "img")
sound_dir = os.path.join(os.path.dirname(__file__), "sound")
img_dir = resource_path("img")
sound_dir = resource_path("sound")
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)
project_folder/
main.py
xxx.py # modules
xxx.py # modules
sound/ # directory containing the sound files
img/ # directory containing the image files
venv/ # if using a venv
main.spec
added_files = [
("sound", "sound"),
("img", "img")
]
def img_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)
icon_path = img_resource_path("app/img/app_icon.ico")
root.wm_iconbitmap(icon_path)
added_files = [
('app/img/app_icon.ico','app/img/')
]
a = Analysis(['main.py'],
pathex=['D:\\Github Repos\\Processes-Killer\\Process Killer'],
binaries=[],
datas=added_files,
hiddenimports=[],
hookspath=[],
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
[],
name='Process Killer',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=True , uac_admin=True, icon='app\\img\\app_icon.ico')
pyinstaller.exe "Process Killer.spec"
pyinstaller.py --onefile -F --add-data=images;images --runtime-hook=cp_images_hook.py main.py
import sys
import os
import shutil
path = getattr(sys, '_MEIPASS', os.getcwd())
full_path = path+"\\images"
try:
shutil.move(full_path, ".\\images")
except:
print("Cannot create 'images' folder. Already exists.")
import sys
import os
path = getattr(sys, '_MEIPASS', os.getcwd())
os.chdir(path)
def getPath(filename):
import os
import sys
from os import chdir
from os.path import join
from os.path import dirname
from os import environ
if hasattr(sys, '_MEIPASS'):
# PyInstaller >= 1.6
chdir(sys._MEIPASS)
filename = join(sys._MEIPASS, filename)
elif '_MEIPASS2' in environ:
# PyInstaller < 1.6 (tested on 1.5 only)
chdir(environ['_MEIPASS2'])
filename = join(environ['_MEIPASS2'], filename)
else:
chdir(dirname(sys.argv[0]))
filename = join(dirname(sys.argv[0]), filename)
return filename
datas = [("path/to/mypackage/data_file.txt", "path/to/mypackage")]
mypackage
__init__.py # This is a MUST in order for the package to be registered
data_file.txt # The data file you've added
Hello world!