Matplotlib交互式事件循环

3
Matplotlib如何为像Qt这样的后端库设置事件循环,同时仍然允许通过Python REPL进行交互?至少对于Qt,主事件循环必须在主线程中运行,但那就是REPL所在的位置,因此我很难看到两者如何共存。
我目前的尝试是在一个单独的Python threading.Thread中启动QApplication。
def mainloop():
    app = QtWidgets.QApplication([])
    while True:
        app.processEvents()
        time.sleep(0.01)
t = threading.Thread(target=mainloop)
t.daemon = True
t.start()

这种方法有点可行,但我会收到一个警告,并且它有时会崩溃:

QObject: Cannot create children for a parent that is in a different thread.
(Parent is QApplication(0x7fc5cc001820), parent's thread is QThread(0x7fc5cc001a40), current thread is QThread(0x2702160)

更新 1

以下是使用 QThread 的尝试:

from PyQt5 import QtGui, QtCore, QtWidgets
import time

class Mainloop(QtCore.QObject):
    def __init__(self):
        super().__init__()
        self.app = QtWidgets.QApplication([])

    def run(self):
        while True:
            self.app.processEvents()
            time.sleep(1)

t = QtCore.QThread()
m = Mainloop()
m.moveToThread(t)
t.started.connect(m.run)
t.start()

# Essentially I want to be able to interactively build a GUI here
dialog = QtWidgets.QDialog()
dialog.show()

更新2

基本上,我想要仿照下面的交互式Python会话,也就是不运行它作为脚本来呈现一个准备好的GUI。是什么神奇的方法让图形窗口的外观不会阻塞Python解释器呢?

Python 2.7.13 (default, Jan 19 2017, 14:48:08) 
Type "copyright", "credits" or "license" for more information.

IPython 5.2.2 -- An enhanced Interactive Python.
?         -> Introduction and overview of IPython's features.
%quickref -> Quick reference.
help      -> Python's own help system.
object?   -> Details about 'object', use 'object??' for extra details.

In [1]: import matplotlib.pyplot as plt

In [2]: plt.ion()

In [3]: fig = plt.figure()

In [4]: # The last command opens a figure window which remains responsive.
   ...:  I can resize it and the window redraws, yet I can still interact 
   ...: with the python interpreter and interactively add new content to t
   ...: he figure

In [5]: ax = fig.add_subplot(111)

In [6]: # The last command updated the figure window and I can still inter
   ...: act with the interpreter

In [7]: 

谢谢您的回复。忘了提到我也尝试过使用app.exec_()的不同版本。不确定是否有什么误解。我没有使用app.exec_()的原因是它会在Qt主事件循环中阻塞Python REPL。您能解释一下为什么这样做会起作用吗? - Thomas
我使用QThread尝试更新了问题。在Update 1中,我可以运行代码而不出现错误,但是“QDialog”不响应,也就是说,当我调整窗口大小时,它不会重新绘制窗口,因此我猜测Qt事件循环没有处理事件。 - Thomas
1个回答

3
不是Matplotlib完成的魔法,而是通过IPython实际上称为“魔术命令”的方式完成的。如果你使用ipython --gui=qt启动IPython,或者输入 %gui qt,Qt事件循环将启动并集成到IPython中(--matplotlib选项也可以这样做,但是对于默认后端)。之后,您只需在命令行上创建小部件,而无需启动事件循环即可完成。
~> ipython
Python 3.5.3 |Continuum Analytics, Inc.| (default, Mar  6 2017, 12:15:08)
Type 'copyright', 'credits' or 'license' for more information
IPython 6.1.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: %gui qt

In [2]: from PyQt5 import QtWidgets

In [3]: win = QtWidgets.QPushButton("click me")

In [4]: win.show()

In [5]: win.raise_()

有一个简短的部分讲述了如何在GUI事件循环中集成,详情请参考Integrating with GUI event loop。但要明确的是,您不必遵循这些说明,因为已经为Qt实现了事件循环集成。只需使用%gui qt魔术命令即可。

更新

事实上,您也可以在不使用IPython的情况下做到这一点。 PyQt使得同时拥有常规Python shell和运行的事件循环成为可能,具体请参见PyQt参考指南中Using PyQt5 from the Python Shell部分。不同之处在于需要显式地创建QApplication。例如,输入:

~> python
Python 3.6.0 |Continuum Analytics, Inc.| (default, Dec 23 2016, 13:19:00)
[GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.57)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from PyQt5.QtWidgets import QApplication, QWidget
>>> a = QApplication([])
>>> w = QWidget()
>>> w.show()
>>> w.raise_()

我在思考这个如何与Matplotlib一起使用,并在pyplot源代码中查找了一下。我认为Matplotlib可以处理这两种情况。当你执行plt.ion()时,会调用一个install_repl_displayhook函数。它的文档字符串说:

安装repl显示钩子,以便在控制被返回到repl时自动重绘任何陈旧的图。这适用于IPython终端和内核,以及普通的python shell。

因此,即使IPython不是Matplotlib的依赖项,Matplotlib也知道IPython,并且可以检测它是在IPython shell还是在常规Python shell中。

1
谢谢提供链接,但我不认为这是完整的答案。Matplotlib不是ipython的依赖项吗?既然我可以在没有安装ipython的情况下在常规python shell中运行上面更新2中的代码,但有matplotlib,我不明白所有魔法如何在IPython中发生? - Thomas

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