pythonwindowsqtpyqt5pytest-qt

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


Please note: this is on W10. This may well be significant.

Python: 3.9.4 pytest: 6.2.5 pytest-qt: 4.0.2

I've been using pytest-qt for about a week now to start developing a PyQt5 app. There have been a few baffling problems but none as baffling as this one.

My app code:

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()

The last line above needs to be added. Using a TDD approach I therefore start writing the test:

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)))

NB the commented-out lines show the kind of things I have been trying. But you can see that even just creating an object of the type LogTableView, and then calling the method, with no mocks around at all, causes the error.

On running this:

>pytest -s -v -k test_logger_table.py

I get this:

...
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
...

Has anyone got any idea what this is about?

PS FWIW, out of despair, I even tried this:

super(LogTableView, self).resizeEvent(resize_event)

... same error.


Solution

  • Creating a parent in the child constructor is not a very good idea.

    Remember that PyQt is a binding, every reference used in Python is a wrapper for the Qt object: if the object is deleted on the C++ side, the python reference still exists, but any attempt to use its functions results in the RuntimeError above.

    In your case, there's no persistent reference for the parent on the python side, only the pointer on the Qt side, which is not enough to avoid garbage collection: only parent objects take ownership in Qt (that's why you can avoid persistent references for a child Qt object), it's not the other way around. The problem is that the child believes that it has a parent (as it had one when it was created), but in the meantime that parent has been deleted, as soon as the child constructor is returned.

    Just create a local variable for the 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)
        # ...
    

    Besides the problem of the subject, technically speaking there's no point in using a very specific widget such as QSplitter as a parent (especially considering that in order to be properly used, the widget should be added with addWidget(), as the parenthood alone is pointless for a splitter); if you need a parent, just use a basic QWidget.