Search code examples
pythonpandasdataframepyqt5qmainwindow

QMainWindow unexpectedly closes when I print a pandas DataFrame?


Below is a minimal example that reproduces this problem.

from PyQt5 import QtWidgets
import pandas as pd


class PandasGUI(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()

        # This ensures there is always a reference to this widget and it doesn't get garbage collected
        self._ref = self

        inner = pd.DataFrame([3, 4, 5])

        # Printing the DataFrame makes some windows close/disappear leaving only 3 QMainWindow windows
        # Commenting out this print statement causes 5 windows to be shown as expected
        print(inner)

        self.show()


# Should create 5 PandasGUI windows
app = QtWidgets.QApplication([])
for i in range(5):
    outer = pd.DataFrame([1, 2, 3])
    PandasGUI()

app.exec_()

The problem I have is when I run this code I get 3 or 4 windows shown instead of 5, and I cannot figure out why.

Observations

  • If I remove the self._ref = self line and instead store the widgets in a persistent list object I get 5 windows
  • If I don't create the outer DataFrame I get 5 windows
  • If I inherit a QWidget instead of a QMainWindow I get 5 windows
  • If I add any line inside __init__ that creates a Widget I get 5 windows, such as x = QtWidgets.QPushButton()
  • I cannot reproduce this consistently with different versions of PyQt and pandas other than those below

PyQt==5.13.0
pandas==0.24.2

I reproduced this on two different computers. I have a feeling this is a bug with one of the libraries but would like help understanding the actual root cause here since my example requires such a specific scenario... it would not be useful as a GitHub issue and I don't even know which library is responsible.


Solution

  • It is actually unexpected that the windows don't close. The real bug is in your own code. You are implicitly creating reference-cycles which are randomly keeping objects alive. Python's garbage-collector does not guarantee when or in what order objects will be deleted, so the unpredictable behaviour you see is "normal". If you add import gc; gc.collect() after the for-loop, the windows will probably all be deleted. So the correct solution is to keep explict references to the windows, as you already mentioned in your first bullet-point.