为什么 PyQt 有时会在退出时崩溃?
Why does PyQt sometimes crash on exit?
下面给出的代码显示了一个 QMainWindow
和 4 QGraphicsView
可以用鼠标在其中绘制。它按预期工作,但在关闭它时,控制台中会出现以下错误消息:
Process finished with exit code 139 (interrupted by signal 11: SIGSEGV)
代码有什么问题?
main.py
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QGraphicsView, QGraphicsScene, QGraphicsPathItem
from PyQt5.QtGui import QPainterPath, QPen
from PyQt5.QtCore import Qt
from PyQt5.uic import loadUi
# Based on code from
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
loadUi("mainwindow.ui", self)
self.verticalLayout_top_left.addWidget(GraphicsView())
self.verticalLayout_top_right.addWidget(GraphicsView())
self.verticalLayout_bottom_left.addWidget(GraphicsView())
self.verticalLayout_bottom_right.addWidget(GraphicsView())
class GraphicsView(QGraphicsView):
def __init__(self):
super().__init__()
self.start = None
self.end = None
self.setScene(QGraphicsScene())
self.path = QPainterPath()
self.item = GraphicsPathItem()
self.scene().addItem(self.item)
self.contents_rect = self.contentsRect()
self.setSceneRect(0, 0, self.contents_rect.width(), self.contents_rect.height())
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
def mousePressEvent(self, event):
self.start = self.mapToScene(event.pos())
self.path.moveTo(self.start)
def mouseMoveEvent(self, event):
self.end = self.mapToScene(event.pos())
self.path.lineTo(self.end)
self.start = self.end
self.item.setPath(self.path)
class GraphicsPathItem(QGraphicsPathItem):
def __init__(self):
super().__init__()
pen = QPen()
pen.setColor(Qt.black)
pen.setWidth(5)
self.setPen(pen)
def main():
app = QApplication(sys.argv)
main_window = MainWindow()
main_window.show()
app.exec_()
if __name__ == "__main__":
main()
mainwindow.ui
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="0">
<widget class="QPushButton" name="pushButton_left">
<property name="text">
<string>PushButton</string>
</property>
</widget>
</item>
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout_top_left"/>
</item>
<item row="0" column="1">
<layout class="QVBoxLayout" name="verticalLayout_top_right"/>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="pushButton_right">
<property name="text">
<string>PushButton</string>
</property>
</widget>
</item>
<item row="2" column="0">
<layout class="QVBoxLayout" name="verticalLayout_bottom_left"/>
</item>
<item row="2" column="1">
<layout class="QVBoxLayout" name="verticalLayout_bottom_right"/>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>24</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<resources/>
<connections/>
</ui>
问题是因为即使您完成 运行 app.exec_() 应用程序仍然释放需要访问 QApplication 实例的资源,但在您的情况下 "app" 是通过使 Qt Access 的内部函数成为非预留内存,在这样做之前将其删除。
考虑到上述情况,一个可能的解决方案是扩展"app"的作用域,使其在执行main函数后不被移除,方法是创建一个全局变量。
# ...
<b>app = None</b>
def main():
<b>global app</b>
app = QApplication(sys.argv)
# ...
这是由一个长期存在的问题引起的,该问题将在即将发布的版本(可能是 PyQt-5.14)中得到修复。如果您使用的是 PyQt-5.13.1,则可以使用以下临时 API:
来测试修复
from PyQt5.QtCore import pyqt5_enable_new_onexit_scheme
pyqt5_enable_new_onexit_scheme(True)
如果没有报告此问题,它将最终成为默认行为(因此无需显式启用它)。导致此问题的根本问题记录在此处:
- PyQt5 文档:Crashes On Exit.
本质上,Python 的垃圾回收方案会以不可预测的顺序删除对象,这有时会导致 Qt 尝试删除不再存在的对象(导致崩溃)。所以问题中的例子也可以这样修改:
def main():
app = QApplication(sys.argv)
main_window = MainWindow()
main_window.show()
app.exec_()
# ensure correct deletion order
del main_window, app
上述即将发生的变化意味着将不再需要这种清理代码。
下面给出的代码显示了一个 QMainWindow
和 4 QGraphicsView
可以用鼠标在其中绘制。它按预期工作,但在关闭它时,控制台中会出现以下错误消息:
Process finished with exit code 139 (interrupted by signal 11: SIGSEGV)
代码有什么问题?
main.py
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QGraphicsView, QGraphicsScene, QGraphicsPathItem
from PyQt5.QtGui import QPainterPath, QPen
from PyQt5.QtCore import Qt
from PyQt5.uic import loadUi
# Based on code from
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
loadUi("mainwindow.ui", self)
self.verticalLayout_top_left.addWidget(GraphicsView())
self.verticalLayout_top_right.addWidget(GraphicsView())
self.verticalLayout_bottom_left.addWidget(GraphicsView())
self.verticalLayout_bottom_right.addWidget(GraphicsView())
class GraphicsView(QGraphicsView):
def __init__(self):
super().__init__()
self.start = None
self.end = None
self.setScene(QGraphicsScene())
self.path = QPainterPath()
self.item = GraphicsPathItem()
self.scene().addItem(self.item)
self.contents_rect = self.contentsRect()
self.setSceneRect(0, 0, self.contents_rect.width(), self.contents_rect.height())
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
def mousePressEvent(self, event):
self.start = self.mapToScene(event.pos())
self.path.moveTo(self.start)
def mouseMoveEvent(self, event):
self.end = self.mapToScene(event.pos())
self.path.lineTo(self.end)
self.start = self.end
self.item.setPath(self.path)
class GraphicsPathItem(QGraphicsPathItem):
def __init__(self):
super().__init__()
pen = QPen()
pen.setColor(Qt.black)
pen.setWidth(5)
self.setPen(pen)
def main():
app = QApplication(sys.argv)
main_window = MainWindow()
main_window.show()
app.exec_()
if __name__ == "__main__":
main()
mainwindow.ui
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="0">
<widget class="QPushButton" name="pushButton_left">
<property name="text">
<string>PushButton</string>
</property>
</widget>
</item>
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout_top_left"/>
</item>
<item row="0" column="1">
<layout class="QVBoxLayout" name="verticalLayout_top_right"/>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="pushButton_right">
<property name="text">
<string>PushButton</string>
</property>
</widget>
</item>
<item row="2" column="0">
<layout class="QVBoxLayout" name="verticalLayout_bottom_left"/>
</item>
<item row="2" column="1">
<layout class="QVBoxLayout" name="verticalLayout_bottom_right"/>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>24</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<resources/>
<connections/>
</ui>
问题是因为即使您完成 运行 app.exec_() 应用程序仍然释放需要访问 QApplication 实例的资源,但在您的情况下 "app" 是通过使 Qt Access 的内部函数成为非预留内存,在这样做之前将其删除。
考虑到上述情况,一个可能的解决方案是扩展"app"的作用域,使其在执行main函数后不被移除,方法是创建一个全局变量。
# ...
<b>app = None</b>
def main():
<b>global app</b>
app = QApplication(sys.argv)
# ...
这是由一个长期存在的问题引起的,该问题将在即将发布的版本(可能是 PyQt-5.14)中得到修复。如果您使用的是 PyQt-5.13.1,则可以使用以下临时 API:
来测试修复from PyQt5.QtCore import pyqt5_enable_new_onexit_scheme
pyqt5_enable_new_onexit_scheme(True)
如果没有报告此问题,它将最终成为默认行为(因此无需显式启用它)。导致此问题的根本问题记录在此处:
- PyQt5 文档:Crashes On Exit.
本质上,Python 的垃圾回收方案会以不可预测的顺序删除对象,这有时会导致 Qt 尝试删除不再存在的对象(导致崩溃)。所以问题中的例子也可以这样修改:
def main():
app = QApplication(sys.argv)
main_window = MainWindow()
main_window.show()
app.exec_()
# ensure correct deletion order
del main_window, app
上述即将发生的变化意味着将不再需要这种清理代码。