【问题标题】:RuntimeError: wrapped C/C++ object of type QPlainTextEdit has been deletedRuntimeError: QPlainTextEdit 类型的包装 C/C++ 对象已被删除
【发布时间】:2021-10-03 19:41:59
【问题描述】:

我正在尝试从 menuBar 启动一个小部件,然后在用户单击按钮时弹出一个 TextEdit 对话框。这一切都很好,但是,如果我要关闭小部件并再次重新启动小部件,我会随机遇到“RuntimeError: QPlainTextEdit 类型的包装 C/C++ 对象已被删除”。

Traceback (most recent call last):
  File "mydirectory", line 373, in startClicked
    self.runF.logger.info('Initializing...')
  File "C:\Program Files\Python\Python37\lib\logging\__init__.py", line 1332, in info
    self._log(INFO, msg, args, **kwargs)
  File "C:\Program Files\Python\Python37\lib\logging\__init__.py", line 1468, in _log
    self.handle(record)
  File "C:\Program Files\Python\Python37\lib\logging\__init__.py", line 1478, in handle
    self.callHandlers(record)
  File "C:\Program Files\Python\Python37\lib\logging\__init__.py", line 1540, in callHandlers
    hdlr.handle(record)
  File "C:\Program Files\Python\Python37\lib\logging\__init__.py", line 854, in handle
    self.emit(record)
  File "mydirectory", line 19, in emit
    self.widget.appendPlainText(self.format(record))
RuntimeError: wrapped C/C++ object of type QPlainTextEdit has been deleted

我无法弄清楚为什么这是一个问题,因为我在小部件初始化期间创建了 TextEdit 对话框的新实例。

以下是我的精简代码,但我似乎无法重现此问题-

mainTest.py

from PyQt5 import  QtCore, QtWidgets
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
import logging
import test1


class mainTest(QMenuBar):

    def __init__(self, parent=None):
        super().__init__()
        logging.basicConfig(filename=fr'testuser_debug.log', level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
        self.setWindowState(self.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive | Qt.MSWindowsFixedSizeDialogHint)
        self.setWindowTitle('test')
        self.setAttribute(Qt.WA_DeleteOnClose)
        self.viewMenu = self.addMenu('&Views')
        self.viewSubMenu = QMenu(self.viewMenu)
        self.viewSubMenu.setTitle('&Tools')
        self.testTool = QAction("&test Tool", self)
        self.viewSubMenu.addAction(self.testTool)
        self.viewMenu.addAction(self.viewSubMenu.menuAction())
        self.testTool.triggered.connect(self.testToolPopup)
        self.setFixedHeight(self.sizeHint().height())
        self.resize(500, self.height())

    def testToolPopup(self):
        try:
            self.third_screen.setWindowState(
                self.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive)
            self.third_screen.activateWindow()
        except Exception as e:
            print(str(e))
            self.third_screen = test1.test1GUI(self)
            self.third_screen.show()

if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    w = mainTest()
    w.show()
    sys.exit(app.exec_())

test1.py

from PyQt5 import QtWidgets
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import logging
from PyQt5.QtGui import *


class QPlainTextEditLogger(logging.Handler):
    def __init__(self, parent=None):
        super().__init__()
        self.widget = QPlainTextEdit(parent)
        self.widget.setReadOnly(True)

    def emit(self, record):
        self.widget.appendPlainText(self.format(record))


class emitLogging(QDialog, QPlainTextEdit):
    def __init__(self, parent=None):
        super().__init__()
        self.parent = parent
        self.logTextBox = QPlainTextEditLogger(self)
        self.logTextBox.setFormatter(logging.Formatter('%(asctime)s - %(message)s'))
        self.logger = logging.getLogger('auction')
        self.logger.addHandler(self.logTextBox)
        self.logger.setLevel(logging.INFO)
        layout = QVBoxLayout()
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        self.runButton = QtWidgets.QPushButton()
        self.runButton.setSizePolicy(sizePolicy)
        sizePolicy.setHeightForWidth(self.runButton.sizePolicy().hasHeightForWidth())
        self.runButton.setText("Run")
        layout.addWidget(self.logTextBox.widget)
        layout.addWidget(self.runButton)
        self.setLayout(layout)

    def closeEvent(self, event):
        try:
            self.parent.setEnabled(True)
        except Exception as e:
            logging.error(e)
            print(e)

class test1GUI(QWidget):

    def __init__(self, parent=None):
        super().__init__()
        try:
            self.parent = parent
            self.setWindowTitle("test1 Tool")
            self.setAttribute(Qt.WA_DeleteOnClose)
            self.runF = emitLogging(self)

            self.genButton = QtWidgets.QPushButton()
            self.genButton.setText("Generate")
            self.genButton.clicked.connect(self.genClicked)

            self.tda = TDA(self)
            self.tda.status.connect(self.logggin)

            self.vboxB = QVBoxLayout()
            self.vboxB.addWidget(self.genButton)
            self.setLayout(self.vboxB)
        except Exception as e:
            print(e)
            logging.error(e)

    def genClicked(self):
        try:
            self.setEnabled(False)
            self.runF.setEnabled(True)
            self.runF.logTextBox.widget.clear()
            self.tda._process_call()
            self.runF.show()
        except Exception as e:
            print(str(e))

    @pyqtSlot(str)
    def logggin(self, txt):
        try:
            self.runF.logger.info(txt)
        except Exception as e:
            print(e)

class TDA(QObject):

    status = pyqtSignal(str)

    def __init__(self, parent=None):
        super().__init__()
        self.parent = parent

    def _process_call(self):
        self.status.emit('test....')
        print('here')

【问题讨论】:

  • 嗯,你确实添加了self.setAttribute(Qt.WA_DeleteOnClose)...
  • @musicamante 应该没问题吧?当我关闭它时它会清理小部件,然后通过 self.third_screen = test1.test1GUI(self) 重新启动
  • @Ken testToolPopup 中的try/except 块确保仅当self 还没有'third_screen' 属性时才创建新实例。关闭对话框将删除底层 C++ 对象,但 Python 包装器将被留下。 Qt 显然对 Python 一无所知,所以self.third_screen 属性不会被删除。当再次调用 testToolPopup 时,您的代码会尝试调用 self.third_screen.setWindowState(...),由于这是继承的 Qt 方法,因此将引发运行时错误(因为底层 C++ 对象不再存在)。

标签: python pyqt5


【解决方案1】:

WA_DeleteOnClose 属性只删除小部件,而不是它的 python 引用。结果是,当你再次调用testToolPopup 时,访问self.third_screen 不会引发AttributeError 异常(对象存在),但会引发RuntimeError 一个,因为包装的对象已被上述设置破坏并且无法访问其 Qt 属性和函数。

记住Qt是一个绑定,每一个用来引用Qt对象的python对象实际上都是一个实际 C++对象的包装器:

  • 表示Qt对象的python对象可以被删除,Qt对象仍然存在;这是emitLogging中的layout的情况:python对象在__init__之后被垃圾收集,但实际的QLayout存在,因为它的所有权属于小部件,并且可以使用layout = self.layout()再次从python访问;
  • Qt 对象可能会被销毁,并且引用它的 python 对象仍然存在,直到手动 deleted 或垃圾收集;

后者正是你的情况,因为包装的 QWidget 在关闭后被销毁,即使 self.third_screen 仍然存在。由于被包装的对象被销毁,PyQt 无法正确访问其属性,例如setWindowStateactivateWindow

在您的情况下,一种可能性是连接到 destroyed 信号并删除 python 引用:

    def testToolPopup(self):
        # ...
        except Exception as e:
            # ...
            self.third_screen.destroyed.connect(self.third_destroyed)

    def third_destroyed(self):
        del self.third_screen

【讨论】:

  • 我明白你的意思,并同意一旦我关闭小部件并尝试重新打开它,我得到的错误是 RuntimeError。但是,我的印象是异常是通过self.third_screen = test1.test1GUI(self) 将 python 对象 self.third_screen 重新分配给新的小部件实例来处理的,这不是有效地将 python 对象设置为新的 Qt 对象吗?类似于说 x=1 x=2 x 现在是 2。
  • @Ken 我们只能根据您提供的代码给出答案。异常是受管理的,我只能指出为什么会引发它。如果您收到“随机错误”,则表示您提供的代码不会重现该问题。因此,您要么发现原始代码有什么问题,要么尝试找到一种方法来实际重现它并将 MRE 提供给我们。
猜你喜欢
  • 2020-11-10
  • 2019-06-05
  • 2020-08-29
  • 1970-01-01
  • 2021-06-02
  • 1970-01-01
  • 2013-07-28
  • 2020-06-01
  • 1970-01-01
相关资源
最近更新 更多