如何像 Matplotlib 的离子模式一样异步使用 At 运行 进行交互?

How to have Qt run asynchroneously for interactive use like Matplotlib's ion mode?

我希望能够从 python 解释器启动 Qt 界面,命令行立即返回,这样我就可以在能够使用界面的同时继续使用 python。基本上,我希望能够像使用 matplotlib 的 ion 交互模式、jupyter notebook 或 Matlab 那样从解释器与 GUI 进行交互。

我天真地尝试将应用程序的执行放在一个线程中:

from PyQt5.QtCore import QThread
from PyQt5.QtWidgets import QApplication, QGraphicsRectItem, QGraphicsScene, QGraphicsView, QMainWindow

class Rect(QGraphicsRectItem):
  def mousePressEvent(self, event):
    print("foo")

app = QApplication([])

class AppThread(QThread):
  def run(self):
    app.exec()
    print('bar')


window = QMainWindow()
window.setGeometry(100, 100, 400, 400)
view = QGraphicsView()
scene = QGraphicsScene()
rect = Rect(0, 0, 150, 150)
scene.addItem(rect)
view.setScene(scene)
window.setCentralWidget(view)
window.show()

thread = AppThread()
thread.start()

...但这不起作用,因为生成的 GUI 已冻结,因此无法使用。

matplotlib 的后端之一是 Qt5Agg,我天真地认为这意味着我可以使用 PyQt5 或 PySide2 来实现类似的效果。

真的可以实现吗?

不需要使用线程或另一个库的补充,你只需要执行命令但你不应该调用 QApplication 的 exec_() 方法,因为它使用 python 交互式控制台事件循环。

$ python
Python 3.8.2 (default, Feb 26 2020, 22:21:03) 
[GCC 9.2.1 20200130] on linux
Type "help", "copyright", "credits" or "license" for more information
>>> from PyQt5.QtWidgets import QApplication, QGraphicsRectItem, QGraphicsScene, QGraphicsView, QMainWindow
>>> class Rect(QGraphicsRectItem):
...   def mousePressEvent(self, event):
...     print("foo")
... 
>>> app = QApplication([])
>>> window = QMainWindow()
>>> window.setGeometry(100, 100, 400, 400)
>>> view = QGraphicsView()
>>> scene = QGraphicsScene()
>>> rect = Rect(0, 0, 150, 150)
>>> scene.addItem(rect)
>>> view.setScene(scene)
>>> window.setCentralWidget(view)
>>> window.show()

IPython

正如 IPython docs 指出的那样,必须使用 %gui backend 来启用 GUI 事件循环。在PyQt5/PySide2的情况下,必须在开头使用%gui qt5

$ ipython
Python 3.8.2 (default, Feb 26 2020, 22:21:03) 
Type 'copyright', 'credits' or 'license' for more information
IPython 7.13.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: %gui qt5                                                                                                                                                                                

In [2]: from PyQt5.QtWidgets import QApplication, QGraphicsRectItem, QGraphicsScene, QGraphicsView, QMainWindow                                                                                 

In [3]: class Rect(QGraphicsRectItem): 
   ...:   def mousePressEvent(self, event): 
   ...:     print("foo") 
   ...:                                                                                                                                                                                         

In [4]: app = QApplication([])                                                                                                                                                                  

In [5]: window = QMainWindow()                                                                                                                                                                  

In [6]: window.setGeometry(100, 100, 400, 400)                                                                                                                                                  

In [7]: view = QGraphicsView()                                                                                                                                                                  

In [8]: scene = QGraphicsScene()                                                                                                                                                                

In [9]: rect = Rect(0, 0, 150, 150)                                                                                                                                                             

In [10]: scene.addItem(rect)                                                                                                                                                                    

In [11]: view.setScene(scene)                                                                                                                                                                   

In [12]: window.setCentralWidget(view)                                                                                                                                                          

In [13]: window.show()