【问题标题】:Path error in PySide2 application after packaging with PyInstaller使用 PyInstaller 打包后 PySide2 应用程序中的路径错误
【发布时间】:2019-07-24 15:00:39
【问题描述】:

我正在尝试使用以下结构打包 PySide2 测试应用程序:

.
├── 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

当我尝试运行可执行文件时出现此错误:

FileNotFoundError: 没有这样的文件或目录:'/home/artem/Desktop/testUI/dist/main/wizardUI'

这是我的 wizard.py 文件

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 是:

from PySide2.QtWidgets import QApplication
from wizardUI.wizard import tutorWizard
import sys


app = QApplication(sys.argv)
window = tutorWizard()
window.show()
sys.exit(app.exec_())

.spec 文件是:

# -*- 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')

有没有办法在 wizard.py 中不更改 current_dir 变量来解决此错误?

【问题讨论】:

    标签: python pyinstaller pyside2


    【解决方案1】:

    您的代码存在以下问题:

    • 您在 COLLECT 之后将 Tree() 添加到 a.datas 中,因此它不会在编译中使用,您必须在之前添加它。

    • 您不能再使用 __file__ 来获取目录路径,而必须使用 sys._MEIPASS

    我还会给出以下改进:

    • 为了使 .spec 具有可移植性,我将使用 SPECPATH 变量。
    • 我添加了第二个参数“wizardUI”来创建带有 .ui 的字典,我还排除了wizard.py。

    综合以上情况,解决方法如下:

    ma​​in.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)
    

    ma​​in.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')
    

    另一种选择是使用Qt Resource 代替数据。

    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>
    

    然后使用 pyside2-rcc 将其转换为 .py:

    pyside2-rcc resource.qrc -o resource_rc.py
    

    然后你必须修改脚本:

    ma​​in.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)
    

    最后你的项目结构如下:

    ├── 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
    

    两种解决方案都找到here

    【讨论】:

    • 感谢您的解决方案。不幸的是,打包后应用程序不起作用。它进入一个无限循环。杀死一个进程后,我不时收到以下错误File "wizardUI/wizard.py", line 19, in findPages while it.hasNext(): KeyboardInterrupt[16227] Failed to execute script main
    • @Artem 您是否能够运行 main.py 而无需将其转换为可执行文件?
    • @Artem 这对我来说似乎很奇怪,它对我来说是正确的。我要寻找另一种选择
    【解决方案2】:

    这是路径的问题,

    简单来说,

    我们应该使用这个条件来获取ui文件的路径:

        if getattr(sys, 'frozen', False):
            ui_file_path = os.path.join(sys._MEIPASS, ui_file)
        else:
            ui_file_path = os.path.join(sys.path[0], ui_file)
    

    【讨论】:

      猜你喜欢
      • 2015-09-24
      • 2021-08-07
      • 1970-01-01
      • 2021-01-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-04-09
      • 1970-01-01
      相关资源
      最近更新 更多