使用 PyInstaller 打包后 PySide2 应用程序中的路径错误 [英] Path error in PySide2 application after packaging with PyInstaller
问题描述
我正在尝试使用以下结构打包 PySide2 测试应用程序:
<预><代码>.├── main.py├── main.spec└── 精灵UI├── 10.toolBoxBtns.ui├── 11.toolBoxShrCt.ui├── 12.propertyBox.ui├── 13.printing.ui├── 14.settings.ui├── 15.coclusion.ui├── 1.welcomePage.ui├── 2.graphicsScene.ui├── 3.graphicsSceneText.ui├── 4.textDialog.ui├── 5.codeDialog.ui├── 6.graphicsSceneBox.ui├── 7.graphicsScenePixmap.ui├── 8.graphicsSceneShrCt.ui├── 9.toolbox.ui└── 精灵.py当我尝试运行可执行文件时出现此错误:
<块引用>FileNotFoundError: No such file or directory:'/home/artem/Desktop/testUI/dist/main/wizardUI'
这是我的 Wizard.py 文件
from PySide2 import QtCore, QtWidgets从 PySide2.QtUiTools 导入 QUiLoader导入操作系统类导师向导(QtWidgets.QWizard):""" 包含介绍教程 """def __init__(self, parent=None):super(tutorWizard, self).__init__(parent)self.setWindowTitle("介绍教程")pages = self.findPages()self.initPages(页数)def findPages(self):ui_files = []cnt = 1current_dir = os.path.dirname(os.path.realpath(__file__))而 len(ui_files) != 15:对于 os.listdir(current_dir) 中的文件:如果 file.startswith("{}.".format(cnt)):ui_files.append(os.path.join(current_dir, file))cnt += 1返回 ui_filesdef initPages(self, files):加载器 = QUiLoader()对于我在文件中:文件 = QtCore.QFile(str(i))file.open(QtCore.QFile.ReadOnly)文件.重置()页面 = loader.load(file)文件.close()self.addPage(页面)
main.py 是:
from PySide2.QtWidgets import QApplication从wizardUI.wizard 导入tutorWizard导入系统app = QApplication(sys.argv)窗口 = 导师向导()window.show()sys.exit(app.exec_())
和 .spec 文件是:
# -*- mode: python ;编码:utf-8 -*-block_cipher = 无a = Analysis(['main.py'],pathex=['/home/artem/Desktop/testUI'],二进制文件=[],数据=[],hiddenimports=['PySide2.QtXml'],钩子路径=[],runtime_hooks=[],排除=[],win_no_prefer_redirects=假,win_private_assemblies=假,密码=块密码,noarchive=假)pyz = PYZ(a.pure, a.zipped_data,密码=block_cipher)exe = EXE(pyz,a. 脚本,[],exclude_binaries=真,名称='主要',调试=假,bootloader_ignore_signals=假,条=假,upx=真,控制台=真)科尔 = 收集(exe,a.二进制文件,a.zip 文件,a.数据,条=假,upx=真,upx_exclude=[],名称='主要')a.datas += Tree('/home/artem/Desktop/testUI/wizardUI')
有没有办法在 wizard.py 中不用修改 current_dir
变量来解决这个错误?
你的代码有以下问题:
您在 COLLECT 之后将
Tree()
添加到 a.datas 中,因此它不会在编译中使用,您必须在此之前添加.您不能再使用 __file__ 来获取目录路径,而必须使用 sys._MEIPASS.
我还将进行以下改进:
- 为了使 .spec 可移植,我将使用 SPECPATH 变量.
- 我添加了第二个参数wizardUI"以使用 .ui 创建字典,我还排除了 Wizard.py.
综合以上,解决方案如下:
main.py
from PySide2.QtWidgets import QApplication从wizardUI.wizard 导入tutorWizard导入系统如果 __name__ == "__main__":app = QApplication(sys.argv)窗口 = 导师向导()window.show()sys.exit(app.exec_())
wizard.py
导入操作系统导入系统从 PySide2 导入 QtCore、QtWidgets、QtUiTools# https://stackoverflow.com/a/42615559/6622587如果 getattr(sys, 'frozen', False):# 如果应用程序作为一个包运行,pyInstaller 引导加载程序# 通过一个标志frozen=True 扩展sys 模块并设置应用程序# 进入变量 _MEIPASS' 的路径.current_dir = os.path.join(sys._MEIPASS, "wizardUI")别的:current_dir = os.path.dirname(os.path.abspath(__file__))类导师向导(QtWidgets.QWizard):"""包含介绍教程"""def __init__(self, parent=None):super(tutorWizard, self).__init__(parent)self.setWindowTitle("介绍教程")pages = self.findPages()self.initPages(页数)def findPages(self):ui_files = []cnt = 1而 len(ui_files) <15:对于 os.listdir(current_dir) 中的文件:如果 file.startswith("{}.".format(cnt)):ui_files.append(os.path.join(current_dir, file))cnt += 1返回 ui_filesdef initPages(self, files):loader = QtUiTools.QUiLoader()对于我在文件中:文件 = QtCore.QFile(str(i))如果 file.open(QtCore.QFile.ReadOnly):页面 = loader.load(file)self.addPage(页面)
main.spec
# -*- 模式:python ;编码:utf-8 -*-# https://stackoverflow.com/a/50402636/6622587导入操作系统spec_root = os.path.abspath(SPECPATH)block_cipher = 无a = Analysis(['main.py'],pathex=[spec_root],二进制文件=[],数据=[],hiddenimports=['PySide2.QtXml', 'packaging.specifiers', 'packaging.requirements'],钩子路径=[],runtime_hooks=[],排除=[],win_no_prefer_redirects=假,win_private_assemblies=假,密码=块密码,noarchive=假)pyz = PYZ(a.pure, a.zipped_data,密码=block_cipher)exe = EXE(pyz,a. 脚本,[],exclude_binaries=真,名称='主要',调试=假,bootloader_ignore_signals=假,条=假,upx=真,控制台=真)a.datas += Tree(os.path.join(spec_root, 'wizardUI'), 'wizardUI', excludes=["*.py"])科尔 = 收集(exe,a.二进制文件,a.zip 文件,a.数据,条=假,upx=真,upx_exclude=[],名称='主要')
<小时>
另一种选择是使用 Qt Resource
而不是数据.
resource.qrc
<qresource prefix="/"><file>wizardUI/1.welcomePage.ui</file><file>wizardUI/2.graphicsScene.ui</file><file>wizardUI/3.graphicsSceneText.ui</file><file>wizardUI/4.textDialog.ui</file><file>wizardUI/5.codeDialog.ui</file><file>wizardUI/6.graphicsSceneBox.ui</file><file>wizardUI/7.graphicsScenePixmap.ui</file><file>wizardUI/8.graphicsSceneShrCt.ui</file><file>wizardUI/9.toolbox.ui</file><file>wizardUI/10.toolBoxBtns.ui</file><file>wizardUI/11.toolBoxShrCt.ui</file><file>wizardUI/12.propertyBox.ui</file><file>wizardUI/13.printing.ui</file><file>wizardUI/14.settings.ui</file><file>wizardUI/15.coclusion.ui</file></qresource></RCC>
然后使用 pyside2-rcc 将其转换为 .py:
pyside2-rcc resource.qrc -o resource_rc.py
然后你必须修改脚本:
main.py
from PySide2.QtWidgets import QApplication从wizardUI.wizard 导入tutorWizard导入系统导入资源_rc如果 __name__ == "__main__":app = QApplication(sys.argv)窗口 = 导师向导()window.show()sys.exit(app.exec_())
wizard.py
from PySide2 import QtCore, QtWidgets, QtUiTools类导师向导(QtWidgets.QWizard):""" 包含介绍教程 """def __init__(self, parent=None):super(tutorWizard, self).__init__(parent)self.setWindowTitle("介绍教程")pages = self.findPages()self.initPages(页数)def findPages(self):ui_files = []cnt = 1而 len(ui_files) <15:it = QtCore.QDirIterator(":/wizardUI")而 it.hasNext():文件名 = it.next()name = QtCore.QFileInfo(filename).fileName()如果 name.startswith("{}.".format(cnt)):ui_files.append(文件名)cnt += 1返回 ui_filesdef initPages(self, files):loader = QtUiTools.QUiLoader()对于我在文件中:文件 = QtCore.QFile(str(i))如果 file.open(QtCore.QFile.ReadOnly):页面 = loader.load(file)self.addPage(页面)
最后你的项目结构如下:
├── main.py├── main.spec├──资源.qrc├── resource_rc.py└── 精灵UI├── 10.toolBoxBtns.ui├── 11.toolBoxShrCt.ui├── 12.propertyBox.ui├── 13.printing.ui├── 14.settings.ui├── 15.coclusion.ui├── 1.welcomePage.ui├── 2.graphicsScene.ui├── 3.graphicsSceneText.ui├── 4.textDialog.ui├── 5.codeDialog.ui├── 6.graphicsSceneBox.ui├── 7.graphicsScenePixmap.ui├── 8.graphicsSceneShrCt.ui├── 9.toolbox.ui└── 精灵.py
<小时>
这两种解决方案都可以在此处
I'm trying to package a PySide2 test application with the following structure:
.
├── main.py
├── main.spec
└── wizardUI
├── 10.toolBoxBtns.ui
├── 11.toolBoxShrCt.ui
├── 12.propertyBox.ui
├── 13.printing.ui
├── 14.settings.ui
├── 15.coclusion.ui
├── 1.welcomePage.ui
├── 2.graphicsScene.ui
├── 3.graphicsSceneText.ui
├── 4.textDialog.ui
├── 5.codeDialog.ui
├── 6.graphicsSceneBox.ui
├── 7.graphicsScenePixmap.ui
├── 8.graphicsSceneShrCt.ui
├── 9.toolbox.ui
└── wizard.py
When I try to run an executable I get this error:
FileNotFoundError: No such file or directory:'/home/artem/Desktop/testUI/dist/main/wizardUI'
Here's my wizard.py file
from PySide2 import QtCore, QtWidgets
from PySide2.QtUiTools import QUiLoader
import os
class tutorWizard(QtWidgets.QWizard):
""" Contains introduction tutorial """
def __init__(self, parent=None):
super(tutorWizard, self).__init__(parent)
self.setWindowTitle("Introduction tutorial")
pages = self.findPages()
self.initPages(pages)
def findPages(self):
ui_files = []
cnt = 1
current_dir = os.path.dirname(os.path.realpath(__file__))
while len(ui_files) != 15:
for file in os.listdir(current_dir):
if file.startswith("{}.".format(cnt)):
ui_files.append(os.path.join(current_dir, file))
cnt += 1
return ui_files
def initPages(self, files):
loader = QUiLoader()
for i in files:
file = QtCore.QFile(str(i))
file.open(QtCore.QFile.ReadOnly)
file.reset()
page = loader.load(file)
file.close()
self.addPage(page)
main.py is:
from PySide2.QtWidgets import QApplication
from wizardUI.wizard import tutorWizard
import sys
app = QApplication(sys.argv)
window = tutorWizard()
window.show()
sys.exit(app.exec_())
and .spec file is:
# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
a = Analysis(['main.py'],
pathex=['/home/artem/Desktop/testUI'],
binaries=[],
datas=[],
hiddenimports=['PySide2.QtXml'],
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,
[],
exclude_binaries=True,
name='main',
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='main')
a.datas += Tree('/home/artem/Desktop/testUI/wizardUI')
Is there any way to solve this error without changig current_dir
variable in wizard.py ?
Your code has the following problems:
You are adding the
Tree()
to a.datas after COLLECT so it will not be used in the compilation, you have to add it before.You can no longer use __file__ to get the directory path, instead you must use sys._MEIPASS.
I will also give the following improvements:
- For the .spec to be portable, I will use the SPECPATH variable.
- I have added as a second parameter "wizardUI" to create a dictionary with the .ui, I have also excluded wizard.py.
Considering the above, the solution is as follows:
main.py
from PySide2.QtWidgets import QApplication
from wizardUI.wizard import tutorWizard
import sys
if __name__ == "__main__":
app = QApplication(sys.argv)
window = tutorWizard()
window.show()
sys.exit(app.exec_())
wizard.py
import os
import sys
from PySide2 import QtCore, QtWidgets, QtUiTools
# https://stackoverflow.com/a/42615559/6622587
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'.
current_dir = os.path.join(sys._MEIPASS, "wizardUI")
else:
current_dir = os.path.dirname(os.path.abspath(__file__))
class tutorWizard(QtWidgets.QWizard):
""" Contains introduction tutorial """
def __init__(self, parent=None):
super(tutorWizard, self).__init__(parent)
self.setWindowTitle("Introduction tutorial")
pages = self.findPages()
self.initPages(pages)
def findPages(self):
ui_files = []
cnt = 1
while len(ui_files) < 15:
for file in os.listdir(current_dir):
if file.startswith("{}.".format(cnt)):
ui_files.append(os.path.join(current_dir, file))
cnt += 1
return ui_files
def initPages(self, files):
loader = QtUiTools.QUiLoader()
for i in files:
file = QtCore.QFile(str(i))
if file.open(QtCore.QFile.ReadOnly):
page = loader.load(file)
self.addPage(page)
main.spec
# -*- mode: python ; coding: utf-8 -*-
# https://stackoverflow.com/a/50402636/6622587
import os
spec_root = os.path.abspath(SPECPATH)
block_cipher = None
a = Analysis(['main.py'],
pathex=[spec_root],
binaries=[],
datas=[],
hiddenimports=['PySide2.QtXml', 'packaging.specifiers', 'packaging.requirements'],
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,
[],
exclude_binaries=True,
name='main',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
console=True )
a.datas += Tree(os.path.join(spec_root, 'wizardUI'), 'wizardUI', excludes=["*.py"])
coll = COLLECT(exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
upx_exclude=[],
name='main')
Another option is to use Qt Resource
instead of data.
resource.qrc
<RCC>
<qresource prefix="/">
<file>wizardUI/1.welcomePage.ui</file>
<file>wizardUI/2.graphicsScene.ui</file>
<file>wizardUI/3.graphicsSceneText.ui</file>
<file>wizardUI/4.textDialog.ui</file>
<file>wizardUI/5.codeDialog.ui</file>
<file>wizardUI/6.graphicsSceneBox.ui</file>
<file>wizardUI/7.graphicsScenePixmap.ui</file>
<file>wizardUI/8.graphicsSceneShrCt.ui</file>
<file>wizardUI/9.toolbox.ui</file>
<file>wizardUI/10.toolBoxBtns.ui</file>
<file>wizardUI/11.toolBoxShrCt.ui</file>
<file>wizardUI/12.propertyBox.ui</file>
<file>wizardUI/13.printing.ui</file>
<file>wizardUI/14.settings.ui</file>
<file>wizardUI/15.coclusion.ui</file>
</qresource>
</RCC>
Then convert it to .py using pyside2-rcc:
pyside2-rcc resource.qrc -o resource_rc.py
Then you have to modify the scripts:
main.py
from PySide2.QtWidgets import QApplication
from wizardUI.wizard import tutorWizard
import sys
import resource_rc
if __name__ == "__main__":
app = QApplication(sys.argv)
window = tutorWizard()
window.show()
sys.exit(app.exec_())
wizard.py
from PySide2 import QtCore, QtWidgets, QtUiTools
class tutorWizard(QtWidgets.QWizard):
""" Contains introduction tutorial """
def __init__(self, parent=None):
super(tutorWizard, self).__init__(parent)
self.setWindowTitle("Introduction tutorial")
pages = self.findPages()
self.initPages(pages)
def findPages(self):
ui_files = []
cnt = 1
while len(ui_files) < 15:
it = QtCore.QDirIterator(":/wizardUI")
while it.hasNext():
filename = it.next()
name = QtCore.QFileInfo(filename).fileName()
if name.startswith("{}.".format(cnt)):
ui_files.append(filename)
cnt += 1
return ui_files
def initPages(self, files):
loader = QtUiTools.QUiLoader()
for i in files:
file = QtCore.QFile(str(i))
if file.open(QtCore.QFile.ReadOnly):
page = loader.load(file)
self.addPage(page)
And finally the structure of your project is as follows:
├── main.py
├── main.spec
├── resource.qrc
├── resource_rc.py
└── wizardUI
├── 10.toolBoxBtns.ui
├── 11.toolBoxShrCt.ui
├── 12.propertyBox.ui
├── 13.printing.ui
├── 14.settings.ui
├── 15.coclusion.ui
├── 1.welcomePage.ui
├── 2.graphicsScene.ui
├── 3.graphicsSceneText.ui
├── 4.textDialog.ui
├── 5.codeDialog.ui
├── 6.graphicsSceneBox.ui
├── 7.graphicsScenePixmap.ui
├── 8.graphicsSceneShrCt.ui
├── 9.toolbox.ui
└── wizard.py
Both solutions are found here
这篇关于使用 PyInstaller 打包后 PySide2 应用程序中的路径错误的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!