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

PyQt modules can't be imported after QtWidgets.QApplication(sys.argv)

概览

我在该行之后导入模块时遇到问题 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 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 上用几个 virtualenvs 测试:

Pyqt 是使用 pip 安装在 virtualenvs 上的,版本是这些:

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

相关信息

来自 #pyqt freenode 频道的一些非常好的人帮助我测试了 repo,其中 none 能够重现该问题,他们使用的 python 版本和平台是:

问题

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

以下是我在对该问题的第一条评论中建议的最小测试用例。它只测试 一个 事情:在创建 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(
>

所以看起来 Qt 模块在导入时有特殊处理 之后创建了一个 QApplication - 虽然 一些 QWebEngine 类 可用,其中大部分已被省略(例如 QWebEngineViewQWebEnginePage 等)。但似乎在您的特定设置中,这可能无法正常工作。如果是这样,您可能需要与 PyQt 的作者讨论这个问题,因为它可能需要了解 Qt 模块的内部工作原理。

导入库后在代码中使用这些行会有所帮助。

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)