为啥我会在 pytest-qt 中收到此“包装的 C/C++ 对象...已被删除”错误?

Posted

技术标签:

【中文标题】为啥我会在 pytest-qt 中收到此“包装的 C/C++ 对象...已被删除”错误?【英文标题】:Why am I getting this "wrapped C/C++ object ... has been deleted" error with pytest-qt?为什么我会在 pytest-qt 中收到此“包装的 C/C++ 对象...已被删除”错误? 【发布时间】:2021-11-05 18:24:35 【问题描述】:

请注意:这是在 W10。这可能很重要。

Python:3.9.4 pytest:6.2.5 pytest-qt: 4.0.2

我已经使用 pytest-qt 大约一个星期来开始开发 PyQt5 应用程序。有一些莫名其妙的问题,但没有一个像这个那样莫名其妙。

我的应用代码:

class LogTableView(QtWidgets.QTableView):    
    def __init__(self, parent, *args, **kwargs):
        super().__init__(parent, *args, **kwargs)

    def resizeEvent(self, resize_event):
        super().resizeEvent(resize_event)
        # self.resizeRowsToContents()

需要添加上面的最后一行。因此,我使用 TDD 方法开始编写测试:

def test_resize_event_should_result_in_resize_rows(request, qtbot):
    t_logger.info(f'\n>>>>>> test name: request.node.originalname')
    table_view = logger_table.LogTableView(QtWidgets.QSplitter())
    # with unittest.mock.patch.object(table_view, 'resizeRowsToContents') as mock_resize:
    # with unittest.mock.patch('logger_table.LogTableView.resizeRowsToContents') as mock_resize:
    table_view.resizeEvent(QtGui.QResizeEvent(QtCore.QSize(10, 10), QtCore.QSize(20, 20)))

注意,注释掉的行显示了我一直在尝试的那种事情。但是您可以看到,即使只是创建一个 LogTableView 类型的对象,然后调用该方法,完全没有任何模拟,也会导致错误。

运行时:

>pytest -s -v -k test_logger_table.py

我明白了:

...
self = <logger_table.LogTableView object at 0x000002B672697670>
resize_event = <PyQt5.QtGui.QResizeEvent object at 0x000002B672743940>

    def resizeEvent(self, resize_event):
>       super().resizeEvent(resize_event)
E       RuntimeError: wrapped C/C++ object of type LogTableView has been deleted
...

有人知道这是怎么回事吗?

PS FWIW,出于绝望,我什至尝试了这个:

super(LogTableView, self).resizeEvent(resize_event)

...同样的错误。

【问题讨论】:

也许这正是错误所说的?你有minimal reproducible example吗? 【参考方案1】:

子构造函数中创建父级并不是一个好主意。

请记住,PyQt 是一个绑定,Python 中使用的每个引用都是 Qt 对象的 包装器:如果在 C++ 端删除该对象,则 Python 引用仍然存在,但是任何尝试使用它的函数都会导致上面的 RuntimeError。

在您的情况下,python 端没有对父级的持久引用,只有 Qt 端的指针,这 不足以 避免垃圾收集:只有 parent em> 对象在 Qt 中拥有所有权(这就是为什么您可以避免对子 Qt 对象进行持久引用的原因),而不是相反。问题是子相信它有一个父级(就像它在创建时有一个),但与此同时,一旦子构造函数返回,这个父级就被删除了。

只需为父级创建一个局部变量。

def test_resize_event_should_result_in_resize_rows(request, qtbot):
    t_logger.info(f'\n>>>>>> test name: request.node.originalname')
    parent = QtWidgets.QSplitter()
    table_view = logger_table.LogTableView(parent)
    # ...

除了主题的问题,从技术上讲,使用 QSplitter 等非常具体的小部件作为父级没有意义(特别是考虑到要正确使用,小部件应该添加addWidget() ,因为单独的父母身份对于分裂者来说毫无意义);如果您需要父母,只需使用基本的 QWidget。

【讨论】:

谢谢!惊人的。是的,事实上我应该通过QWidget 提交问题(正如我已经尝试过的那样)。事实上,我已经大大简化了事情:QTableView 构造函数(在我的问题中没有任何作用!)实际上检查提交的父级是QSplitter。万万没想到,把parent构造成构造函数的参数,会有这样的效果! PS 我不完全确定你的解释是正确的。我刚刚做了一个实验:在正常的应用程序代码中,在 PyQt5 中将父级构造为子级的参数似乎是可以的(尽管在实践中通常不会出现这种情况)。我怀疑这次失败的原因是因为测试上下文:一旦代码超出创建LogTableView 的行,这与unittestpytest 的操作方式有关,这意味着底层C++ 对象已经走了(虽然我不明白到底是什么)。 @mikerodent 这取决于父级的构造方式以及子级对其的反应。我现在无法进行更深入的测试,但我昨天做了几个基本实验,在这两种情况下,应用程序都按预期崩溃了。可能有“不同”的方式可能(或不能)崩溃,测试环境只是改变了一些情况,但它由于垃圾收集和内存/引用管理,因为这正是什么会生成有关被删除的 C++ 对象的异常。由于这不能被视为正常使用(C++ 不允许这样做),因此这是意料之中的。 好的,明白了。我将来会避免这种做法:最坏的情况是这种不一致的错误。我想知道 Python 中是否有其他做法(在 PyQt5 上下文中)可能会转化为 C++ 中的“非法”技术...... @mikerodent 不幸的是,我对 C++ 的经验并不如我所愿,但学习原理和 Qt 源代码无疑是一个好的开始。到目前为止我发现的一个非常有用的来源是weboq Qt source viewer,因为它为几乎所有函数和变量提供了非常有用的工具提示和参考。通过这些,您可以更好、更深入地了解 Qt 实际 的工作原理,尤其是 PyQt 的“底层”。从那里您可以对 C++(和 Qt)的工作原理进行一些研究,并最终了解什么是“更好的实践”。

以上是关于为啥我会在 pytest-qt 中收到此“包装的 C/C++ 对象...已被删除”错误?的主要内容,如果未能解决你的问题,请参考以下文章

为啥我会收到此 Webpack 加载程序错误?

“在 onSaveInstanceState 之后无法执行此操作” - 为啥我会从我的活动的 onResume 方法中收到此异常?

为啥我会收到此错误? “孩子未申报?

知道为啥我会收到此错误吗?

NPM 启动错误上的 ENOENT。为啥我会收到此错误,为啥要查找“我的图片”目录?

为啥我会收到此错误