QtWidgets.QApplication(sys.argv)之后无法导入PyQt模块

3

概述

QtWidgets.QApplication(sys.argv)这一行之后,我遇到了导入模块的问题,假设我有这个小片段main.py:

import sys
import importlib
from PyQt5 import QtWidgets

print('Sys Path:')
print('  %s\n' % '\n  '.join(sys.path))

if sys.argv[-1] == '1':
    print('Importing Before...\n')
    from PyQt5 import Qt
    app = QtWidgets.QApplication(sys.argv)

elif sys.argv[-1] == '2':
    print('Importing After...\n')
    app = QtWidgets.QApplication(sys.argv)
    from PyQt5 import Qt

print('Done')
  • 如果我运行 python main.py 1,一切都按预期工作。
  • 如果我运行 python main.py 2,进程会挂起(可能是在一个无限循环中),但不会显示任何错误信息。

python main.py 2 的输出:

(py352) D:\sources\personal\python\pyqt\mcve>python main.py 2           
Sys Path:                                                               
  D:\sources\personal\python\pyqt\mcve                                  
  D:\sources\personal\python                                            
  d:\virtual_envs\py352\Scripts\python35.zip                            
  d:\virtual_envs\py352\DLLs                                            
  d:\virtual_envs\py352\lib                                             
  d:\virtual_envs\py352\Scripts                                         
  c:\Python352\Lib                                                      
  c:\Python352\DLLs                                                     
  d:\virtual_envs\py352                                                 
  d:\virtual_envs\py352\lib\site-packages                               

Importing After...                                                      
(HANG)

尝试

在win7上使用了几个虚拟环境进行测试:

  • Python 3.5.1 (v3.5.1:37a07cee5969, Dec 6 2015, 01:54:25) [MSC v.1900 64 bit (AMD64)] on win32win7 上运行
  • Python 3.5.2 (v3.5.2:4def2a2901a5, Jun 25 2016, 22:01:18) [MSC v.1900 32 bit (Intel)] on win32

Pyqt 是通过 pip 在虚拟环境中安装的,版本如下:

>>> QtCore.QT_VERSION
329472
>>> QtCore.QT_VERSION_STR
'5.7.0'
>>> QtCore.PYQT_VERSION_STR
'5.7'

相关信息

来自#pyqt freenode频道的一些非常友好的人帮我测试了这个仓库,他们都无法复现该问题,他们使用的Python版本和平台如下:

  • win10 - 3.5.2 |Anaconda 4.1.1 (64位)
  • win8 - 3.5.2 (v3.5.2:4def2a2901a5, Jun 25 2016, 22:01:18)
  • ubuntu 16.04 - 3.5.2 (默认值,Nov 17 2016, 17:05:23)

问题

  • 为什么在我的电脑上from PyQt5 import Qt(或其他pyqt模块)会卡住,而其他人无法复现?
  • 我该如何解决这种行为?这对我很重要,因为我想在我的PyQt应用程序启动后动态加载插件。

一个具体的建议:创建一个测试脚本,仅尝试在创建QApplication之前和之后执行importlib.import_module(mod_name)。如果它仍然挂起,那么你就有了你的mcve;如果它没有挂起,你就会确切地知道你还没有足够调试你的测试用例。应该可以将list_plugins重构为一个独立的函数。然后,您只需将其指向包含*.py文件的任何目录,看看它是否仍然挂起。这个想法是隔离只引起问题的一小部分代码并消除所有其他东西。 - ekhumoro
请您尝试一下我之前评论中提出的两个建议好吗?这只需要大约十分钟的时间,并且它将使您能够发布一个适当的 MCVE。其他方面可能都不重要 - 这几乎肯定是与您特定设置有关的问题。 - ekhumoro
@ekhumoro 好的,我又更新了存储库。如果你有时间,请告诉我是否还有其他可以进一步改善以减少复杂性的地方。 - BPL
你能否把代码放到问题中,这样每个人都可以看到你实际测试的内容?如果你遵循了我之前的建议,你应该只有一个小文件。此外,你需要清楚地说明当你运行测试时会发生什么,因为目前只有你能够重现这个问题。 - ekhumoro
@ekhumoro 好的,我听从了你的建议,现在人们可以在这个问题中看到仓库的内容,或者如果他们发现这是更快的方法,也可以克隆仓库的内容。我无法进一步简化测试用例,正如你所看到的,结果是一组5个文件(不知道为什么你之前说我最终只会得到一个小文件),我仍然不确定将代码放在问题中的好处是什么,而不是使用 git clone...但让我们试一试,因为这个问题已经困扰我几天了。 - BPL
2个回答

3

这并不是一个真正的答案,但它可能提供了找到答案的第一步。

以下是我在问题的第一条评论中建议的最小测试用例。它只测试了一个事情:在创建 QApplication 后调用 importlib.import_module 是否会使解释器在您的系统上挂起?请注意,现在它只尝试从Python标准库中导入一个模块。重要的是要逐步进行,并小心避免引入任何可能混淆的变量。

请按照下面的说明精确地运行此脚本,并将输出添加到您的问题中。(即使选项2没有挂起, sys.path 细节也可能是相关的)。

import sys, importlib
from PyQt5 import QtWidgets

print('Sys Path:')
print('  %s\n' % '\n  '.join(sys.path))

mod = None
modname = 'collections.abc'
# modname = 'PyQt5.Qt'

if sys.argv[-1] == '1':
    print('Importing Before...\n')
    mod = importlib.import_module(modname)
    app = QtWidgets.QApplication(sys.argv)

elif sys.argv[-1] == '2':
    print('Importing After...\n')
    app = QtWidgets.QApplication(sys.argv)
    mod = importlib.import_module(modname)
    # from PyQt5 import Qt

print('Result: %r' % mod)

按照以下方式运行脚本:

$ python /tmp/test.py 1

那么就像这样:
$ python /tmp/test.py 2

在我的系统上(ArchLinux,Python-3.5.2,Qt-5.7.1,PyQt-5.7),第二个产生以下输出:

Sys Path:
  /tmp
  /usr/lib/python35.zip
  /usr/lib/python3.5
  /usr/lib/python3.5/plat-linux
  /usr/lib/python3.5/lib-dynload
  /usr/lib/python3.5/site-packages

Importing After...

Result: <module 'collections.abc' from '/usr/lib/python3.5/collections/abc.py'>

更新:

第一步已经确定importlib本身不是问题的原因。第二步是要确定哪个具体的导入模块是问题的源头。

我在测试脚本中添加了两行(注释掉的)代码,以便完成此操作。第一行检查是否导入PyQt5.Qt会触发卡顿。如果确实如此,则第二行将检查普通的import语句是否也会触发卡顿。

请注意,from PyQt5 import Qt实际上会导入所有内容,包括一些非常庞大且可能有问题的模块,例如QtWebEngineWidgets。因此,需要进一步细化导入以正确确定问题的精确来源。

更新2:

QtWebEngineWidgets是一个众所周知的问题源,通常需要仔细处理。以下解释器会话输出似乎与您当前的问题相关:

>>> from PyQt5 import QtWidgets, QtCore
>>> app = QtWidgets.QApplication([''])
>>> from PyQt5 import QtWebEngineWidgets
Qt WebEngine seems to be initialized from a plugin. Please set Qt::AA_ShareOpenGLContexts using QCoreApplication::setAttribute before constructing QGuiApplication.
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: QtWebEngineWidgets must be imported before a QCoreApplication instance is created
>>>
>>> QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_ShareOpenGLContexts)
>>> from PyQt5 import QtWebEngineWidgets
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: QtWebEngineWidgets must be imported before a QCoreApplication instance is created

这都是正常的行为(虽然目前我找不到任何官方文档来证明)。但让我们尝试使用Qt模块进行相同的操作:

>>> from PyQt5 import QtWidgets, QtCore
>>> QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_ShareOpenGLContexts)
>>> app = QtWidgets.QApplication([''])
>>> from PyQt5 import Qt
>>> Qt.QWeb
Qt.QWebChannel(                      Qt.QWebEngineUrlRequestInterceptor(  Qt.QWebHitTestResult(                Qt.QWebSocketCorsAuthenticator(
Qt.QWebChannelAbstractTransport(     Qt.QWebEngineUrlRequestJob(          Qt.QWebInspector(                    Qt.QWebSocketProtocol(
Qt.QWebDatabase(                     Qt.QWebEngineUrlSchemeHandler(       Qt.QWebPage(                         Qt.QWebSocketServer(
Qt.QWebElement(                      Qt.QWebFrame(                        Qt.QWebPluginFactory(                Qt.QWebView(
Qt.QWebElementCollection(            Qt.QWebHistory(                      Qt.QWebSecurityOrigin(
Qt.QWebEngineCookieStore(            Qt.QWebHistoryInterface(             Qt.QWebSettings(
Qt.QWebEngineUrlRequestInfo(         Qt.QWebHistoryItem(                  Qt.QWebSocket(
>

看起来当QApplication被创建后,Qt模块有特殊处理 - 虽然某些QWebEngine类可用,但大多数都被省略了(例如QWebEngineViewQWebEnginePage等)。但在您的特定设置中,这似乎可能无法正常工作。如果是这样,您可能需要与PyQt的作者联系,因为它可能需要了解Qt模块的内部工作方式。


首先,感谢您的第一次尝试帮助,希望这将激发其他人贡献一些新的想法。我必须说,你把{main_ok.py, main_bug.py}合并到test.py中是个好主意,稍后我会更新repo和问题以简化它。不幸的是,您的测试在我的电脑上没有显示出任何问题(没有挂起)。在我的repo的最后一次提交中,您会看到文件gui\operator_object.py包含from PyQt5 import Qt...但在我得到from PyQt5.Qt import qDrawShadeRect之前,我已经意识到它们两个都在我的电脑上产生了挂起。 - BPL
另外,一个相关的细节是,如果我在 gui\operator_object.py 中将 from PyQt5 import Qt 替换为 import collections.abc,那么我就不会遇到卡顿的问题。因此,似乎是 PyQt 模块让我能够在这里重现这个错误。顺便说一下,感谢您的时间和帮助;D - BPL
@BPL。现在我已经向您展示了如何正确测试事物,您应该能够独立完成其余部分。如果您采取小步骤,仔细检查每个元素的隔离情况,最终您将找到问题的真正根源。如果您更改标题和大部分问题内容,这将有助于解决问题,因为我已经毫无疑问地证明import_module本身并不会导致问题。 - ekhumoro
@BPL。我的开发机是arch-linux,我主要使用官方二进制包,但有时也会从源代码编译。我不知道为什么你看不到我所看到的内容-你是如何测试的?你是从控制台(或Windows上的其他名称)启动Python解释器吗? - ekhumoro
我使用SublimeText和一个包,它允许我在所有可用的虚拟环境中运行我的脚本,我从未使用所有Python系统范围内的安装来执行Python脚本。自从pyqt发布到pypi以来,我再也没有从源代码编译过了。无论如何,非常感谢您的帮助,这个问题确实很棘手。 - BPL
显示剩余5条评论

0
在导入库后,在您的代码中使用这些行将会有所帮助。
from PyQt5 import QtWidgets
app = QtWidgets.QApplication.instance()
if app is not None:
    import sip
    app.quit()
    sip.delete(app)
import sys
from PyQt5 import QtCore, QtWebEngineWidgets
QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_ShareOpenGLContexts)
app = QtWidgets.qApp = QtWidgets.QApplication(sys.argv)

虽然这段代码可能回答了问题,但提供关于它是如何以及为什么解决问题的额外背景信息,将有助于提高答案的长期价值。 - Sven Eberth

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