PyInstaller + UI文件 - FileNotFoundError: [Errno 2] 没有这样的文件或目录:

16
我正在尝试使用PyInstaller将我的.py脚本导出为.exe文件,该脚本依赖于使用Qt Designer创建的.ui文件。
我可以确认当我通过PyCharm运行我的.py脚本时,它能够正常工作-我能够看到我用.ui文件创建的GUI。
然而,当我将我的.py脚本导出为.exe并启动它时,在命令行中会收到以下错误:
C:\Users\giranm>"C:\Users\giranm\PycharmProjects\PyQt Tutorial\dist\secSearch_demo.exe"
Traceback (most recent call last):
  File "secSearch_demo.py", line 13, in <module>
  File "site-packages\PyQt4\uic\__init__.py", line 208, in loadUiType
  File "site-packages\PyQt4\uic\Compiler\compiler.py", line 140, in compileUi
  File "site-packages\PyQt4\uic\uiparser.py", line 974, in parse
  File "xml\etree\ElementTree.py", line 1186, in parse
  File "xml\etree\ElementTree.py", line 587, in parse
FileNotFoundError: [Errno 2] No such file or directory: 'C:\\Users\\giranm\\securitySearchForm.ui'
Failed to execute script secSearch_demo

由于某些原因,.exe文件在路径C:\Users\giranm\中查找.ui文件。
然而,经过一些研究,我被告知需要使用os.getcwd(),并确保我的脚本中有完整的路径。即使使用下面的代码,仍然会出现错误尝试定位.ui文件。 PyInstaller: IOError: [Errno 2] No such file or directory:
# import relevant modules etc...

cwd = os.getcwd()
securitySearchForm = os.path.join(cwd, "securitySearchForm.ui")
popboxForm = os.path.join(cwd, "popbox.ui")

Ui_MainWindow, QtBaseClass = uic.loadUiType(securitySearchForm)
Ui_PopBox, QtSubClass = uic.loadUiType(popboxForm)

# remainder of code below.  

我知道可以使用pyuic4将.ui文件转换为.py文件,并在主程序中导入它们。但是,由于我将对.ui文件进行多次编辑,因此将它们不断转换并不可行。
有没有什么方法可以解决这个问题,使我能够创建一个独立的.exe文件?
我对使用PyQT4和PyInstaller还比较新,任何帮助都将不胜感激!
3个回答

19

在整个周末和 Stack Overflow 上进一步查找后,我终于想出了使用 UI 文件编译独立的 .exe 文件。

首先,我按照这个答案定义了以下函数:

使用 PyInstaller (--onefile) 打包数据文件

# Define function to import external files when using PyInstaller.
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)

下一步,我使用此函数和所需类的变量导入了.UI文件。
# Import .ui forms for the GUI using function resource_path()
securitySearchForm = resource_path("securitySearchForm.ui")
popboxForm = resource_path("popbox.ui")

Ui_MainWindow, QtBaseClass = uic.loadUiType(securitySearchForm)
Ui_PopBox, QtSubClass = uic.loadUiType(popboxForm)

我随后需要使用Qt Designer创建一个资源文件(.qrc),并使用此资源文件嵌入图像/图标。完成后,我使用pyrcc4将.qrc文件转换为.py文件,该文件将被导入到主脚本中。 终端
C:\Users\giranm\PycharmProjects\PyQt Tutorial>pyrcc4 -py3 resources.qrc -o resources_rc.py

Python

import resources_rc

一旦我确认主要的.py脚本可以工作,我就使用PyInstaller创建了一个.spec文件。 终端
C:\Users\giranm\PycharmProjects\PyQt Tutorial>pyi-makespec --noconsole --onefile secSearch_demo.py

根据PyInstaller的指南,我通过修改上述.spec文件来添加数据文件。

https://pythonhosted.org/PyInstaller/spec-files.html#adding-data-files

最后,我使用上述的 .spec 文件编译了 .exe 文件。

每次想要使用open()函数打开通过--add-data属性捆绑的文件时,仍然需要注意以下事项。文件的根路径是sys._MEIPASS,后面跟着项目中的相对路径。 - n1_
Python部署的新手在此:您能否编辑此答案以指定这些代码片段应该放在哪些文件中。 - Gwendal Grelier

5

另一种在Ubuntu 20.04上测试过的方法是将.ui文件添加到规范文件的data部分。首先使用pyinstaller --onefile hello.py生成一个规范文件。然后更新规范文件并运行pyinstaller hello.spec

a = Analysis(['hello.py'],
             ...
             datas=[('mainwindow.ui', '.')],
             ...

下一步是更新Python文件中的当前目录。为此,必须使用os.chdir(sys._MEIPASS)命令。在_MEIPASS未设置时,尝试将其包装在try-catch中以供开发使用。
import os
import sys

# Needed for Wayland applications
os.environ["QT_QPA_PLATFORM"] = "xcb"
# Change the current dir to the temporary one created by PyInstaller
try:
    os.chdir(sys._MEIPASS)
    print(sys._MEIPASS)
except:
    pass

from PySide2.QtUiTools import QUiLoader
from PySide2.QtWidgets import QApplication
from PySide2.QtCore import QFile, QIODevice

if __name__ == "__main__":
    app = QApplication(sys.argv)

    ui_file_name = "mainwindow.ui"
    ui_file = QFile(ui_file_name)
    if not ui_file.open(QIODevice.ReadOnly):
        print(f"Cannot open {ui_file_name}: {ui_file.errorString()}")
        sys.exit(-1)
    loader = QUiLoader()
    window = loader.load(ui_file)
    ui_file.close()
    if not window:
        print(loader.errorString())
        sys.exit(-1)
    window.show()

    sys.exit(app.exec_())

这很简单,对我来说非常有效。无需去更改所有的loadUi()以满足pyinstaller 奇怪的MSWindows操作系统要求!我已经在spec文件中使用了数据文件列表。文件夹版本可以工作,但单个文件版本不行...感谢@Moritz! - Arana

4
你可以简单地使用:
uic.loadUi(r'E:\Development\Python\your_ui.ui', self)

使用完整路径,并使用标准参数使用pyinstaller,就可以正常工作。 r prefix 确保反斜杠按照字面意义进行解释。

似乎 pyinstaller 不理解相对导入!! - SIslam

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