为什么 PyQt 有时会在退出时崩溃?

Ata*_*ore 3 python crash pyqt pyqt5

下面给定的代码显示一个QMainWindow4QGraphicsView以在其中使用鼠标进行绘制。它按预期工作,但关闭它时,控制台中会出现以下错误消息:

Process finished with exit code 139 (interrupted by signal 11: SIGSEGV)
Run Code Online (Sandbox Code Playgroud)

代码有什么问题?


主文件

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 /sf/answers/3097415611/

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()
Run Code Online (Sandbox Code Playgroud)

主窗口.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>
Run Code Online (Sandbox Code Playgroud)

ekh*_*oro 6

这是由一个长期存在的问题引起的,该问题将在即将发布的版本(可能是 PyQt-5.14)中修复。如果您使用的是 PyQt-5.13.1,则可以使用以下临时 API 来测试修复:

from PyQt5.QtCore import pyqt5_enable_new_onexit_scheme

pyqt5_enable_new_onexit_scheme(True)
Run Code Online (Sandbox Code Playgroud)

如果没有报告的问题,它最终将成为默认行为(因此无需显式启用它)。导致该问题的根本问题记录在此处:

本质上,Python 的垃圾收集方案以不可预测的顺序删除对象,这有时会导致 Qt 尝试删除不再存在的对象(导致崩溃)。所以问题中的例子也可以这样修复:

def main():
    app = QApplication(sys.argv)
    main_window = MainWindow()
    main_window.show()
    app.exec_()
    # ensure correct deletion order
    del main_window, app
Run Code Online (Sandbox Code Playgroud)

上面提到的即将发生的变化意味着不再需要这种清理代码。