为什么我在使用 pytest-qt 时会收到此 "wrapped C/C++ object ... has been deleted" 错误?

Why am I getting this "wrapped C/C++ object ... has been deleted" error with pytest-qt?

请注意:这是在 W10 上进行的。这可能很重要。

Python:3.9.4 测试:6.2.5 pytest-qt: 4.0.2

我已经使用 pytest-qt 大约一个星期来开始开发 PyQt5 应用程序。有一些令人费解的问题,但 none 和这个问题一样令人费解。

我的应用代码:

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)

...同样的错误。

在 child 构造函数中创建 parent 不是一个好主意。

请记住,PyQt 是一个 绑定,Python 中使用的每个引用都是 Qt [=43] 的 wrapper =]:如果在 C++ 端删除了 object,python 引用仍然存在,但是任何尝试使用它的函数都会导致上面的 RuntimeError。

在您的情况下,python 端没有对 parent 的持久引用,只有 Qt 端的指针,即 not足以避免垃圾收集:只有 parent object 拥有 Qt 的所有权(这就是为什么你可以避免对 child Qt object),不是相反。问题是 child 相信 它有一个 parent (因为它在创建时有一个),但与此同时 parent 已被删除,一旦返回 child 构造函数。

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

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 等非常具体的小部件作为 parent 是没有意义的(特别是考虑到为了正确使用,小部件应添加 addWidget(),因为单独的 parent 罩对于分离器来说毫无意义);如果您需要 parent,只需使用基本的 QWidget。