Reputation: 734
I am making a gui using PyQt4 and matplotlib. I found this very helpful question/answer on making a tabbed gui window that I am trying to use. Everything seems to work fine (with a couple of minor adjustments); however, whenever I go to close the main window I get a "Python quit unexpectedly" error along with a segmentation fault. While this doesn't affect the operation of the program it is rather annoying and I'd like to hunt down the problem.
Now, while trying to figure out what was going on I got down to the following MWE (or minimum broken example if you will)
import sys
import matplotlib
matplotlib.use('Qt4agg')
import matplotlib.pyplot as plt
import numpy as np
from PyQt4 import QtCore
from PyQt4 import QtGui as qt
if __name__ == "__main__":
fig = plt.figure()
x, y = np.random.randn(2, 40)
ax = fig.add_subplot(111)
ax.plot(x, y, 'o')
ax.hold(False)
app = qt.QApplication(sys.argv)
# ui = MplMultiTab(figures=[fig], labels=["yay"])
ui = qt.QMainWindow()
ui.main_frame = qt.QWidget()
vbox = qt.QVBoxLayout()
vbox.addWidget(fig.canvas)
ui.main_frame.setLayout(vbox)
ui.setCentralWidget(ui.main_frame)
ui.show()
sys.exit(app.exec_())
The problem seems to be adding the figure canvas to the window, as if I don't do this and instead make an empty gui (remove vbox.addWidget(fig.canvas)
) everything is fine and there is no segfault.
Can anyone see what is going wrong or is it a bug in matplotlib or pyqt? Also interestingly, If I set up my gui similar to this answer then I don't have a segmentation fault but I can't really figure out what the difference between them is.
For everyone's information I am running this on python 3.5.2 using PyQt verion 4.11.4 with matplotlib version 1.5.3 on OSX 10.11.6.
Upvotes: 2
Views: 1148
Reputation: 2697
As stated in the PyQt documentation:
For any GUI application using Qt, there is precisely one QApplication object, no matter whether the application has 0, 1, 2 or more windows at any given time.
I think what most likely happens here is that a QApplication is silently created by pyplot when generating the figure. This QApplication is used to manage the main event loop of the FigureManager, which is the GUI that is created by pyplot to show the figure onscreen.
So, since a QApplication has already been created with pyplot, I think an error should normally be raised when qt.QApplication(sys.argv)
is called further down in the code, but somewhat Qt does not seem to see it and allow the creation of another QApplication. That is probably what is causing the clash when trying to close the application. I see 3 different options to solve this issue:
1 - Put the pyplot code inside your QApplication, so that pyplot sees it and uses it instead of constructing its own one:
import sys
import matplotlib
matplotlib.use('Qt4agg')
import matplotlib.pyplot as plt
import numpy as np
from PyQt4 import QtGui as qt
if __name__ == '__main__':
app = qt.QApplication(sys.argv)
fig, ax = plt.subplots()
x, y = np.random.randn(2, 40)
ax.plot(x, y, 'o')
ui = qt.QWidget()
vbox = qt.QVBoxLayout()
vbox.addWidget(fig.canvas)
ui.setLayout(vbox)
ui.show()
sys.exit(app.exec_())
2 - Use a pointer to the QApplication already constructed by pyplot instead of creating a new one:
import sys
import matplotlib
matplotlib.use('Qt4agg')
import matplotlib.pyplot as plt
import numpy as np
from PyQt4 import QtGui as qt
if __name__ == '__main__':
fig, ax = plt.subplots()
x, y = np.random.randn(2, 40)
ax.plot(x, y, 'o')
app = qt.QApplication.instance()
ui = qt.QWidget()
vbox = qt.QVBoxLayout()
vbox.addWidget(fig.canvas)
ui.setLayout(vbox)
ui.show()
sys.exit(app.exec_())
3 - As suggested in the matplotlib documentation, avoid to use the pyplot interface when embedding mpl figures in a Qt interface and use the Object Oriented API instead. In this case, your MWE could be rewritten as:
import sys
import matplotlib
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg
import numpy as np
from PyQt4 import QtGui as qt
if __name__ == '__main__':
app = qt.QApplication(sys.argv)
fig = matplotlib.figure.Figure()
canvas = FigureCanvasQTAgg(fig)
ax = fig.add_subplot(111)
x, y = np.random.randn(2, 40)
ax.plot(x, y, 'o')
ui = qt.QWidget()
vbox = qt.QVBoxLayout()
vbox.addWidget(canvas)
ui.setLayout(vbox)
ui.show()
sys.exit(app.exec_())
Upvotes: 3