Title: In PySide6, if a `QMainWindow` has a pushbutton connected to it's own partial function, it could be opened many times, why?
Tags: python-3.x,pyside6

Question: Here is my code:
import sys
from functools import partial

from PySide6.QtWidgets import (QApplication, QGridLayout, QHBoxLayout,
                               QMainWindow, QPushButton, QStatusBar, QWidget)


class UiTest(QMainWindow):
    def __init__(self):
        self.app = QApplication.instance()
        if self.app is None:
            self.app = QApplication(sys.argv)
        super().__init__()

        self.setObjectName(u"test_partial")
        self.centralwidget = QWidget(self)
        self.centralwidget.setObjectName(u"centralwidget")
        self.setWindowTitle("Test partial")

        self.horizontalLayout = QHBoxLayout(self.centralwidget)

        self.PB_no_partial = QPushButton(self.centralwidget)
        self.PB_no_partial.setObjectName(u"PB_no_partial")
        self.PB_no_partial.setText("no partial")
        self.PB_no_partial.clicked.connect(self.PB_no_partial_clicked)

        self.PB_own_partial = QPushButton(self.centralwidget)
        self.PB_own_partial.setObjectName(u"PB_own_partial")
        self.PB_own_partial.setText("own partial")
        self.PB_own_partial.clicked.connect(self.PB_own_partial_clicked)

        self.PB_others_partial = QPushButton(self.centralwidget)
        self.PB_others_partial.setObjectName(u"PB_others_partial")
        self.PB_others_partial.setText("other's partial")
        self.PB_others_partial.clicked.connect(self.PB_others_partial_clicked)

        self.horizontalLayout.addWidget(self.PB_no_partial)
        self.horizontalLayout.addWidget(self.PB_own_partial)
        self.horizontalLayout.addWidget(self.PB_others_partial)

        self.gridLayout = QGridLayout()
        self.gridLayout.setObjectName(u"gridLayout")
        self.gridLayout.addLayout(self.horizontalLayout, 0, 0, 1, 1)
        self.statusbar = QStatusBar(self)
        self.statusbar.setObjectName(u"statusbar")
        self.setStatusBar(self.statusbar)

        self.setCentralWidget(self.centralwidget)
        self.statusbar = QStatusBar(self)

    def PB_no_partial_clicked(self):
        self.no_partial = UiNoPartial()
        self.no_partial.show()
    
    def PB_own_partial_clicked(self):
        self.own_partial = UiOwnPartial()
        self.own_partial.show()
    
    def PB_others_partial_clicked(self):
        if not hasattr(self, "own_partial"):
            self.own_partial = UiOwnPartial()
        self.others_partial = UiOthersPartial(self.own_partial)
        self.others_partial.show()
    
    def show(self):
        super().show()
        sys.exit(self.app.exec())

class UiNoPartial(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("No partial")

        self.setObjectName(u"no_partial")
        self.centralwidget = QWidget(self)
        self.centralwidget.setObjectName(u"centralwidget")

        self.PB_no_partial = QPushButton(self.centralwidget)
        self.PB_no_partial.setObjectName(u"PB_no_partial")
        self.PB_no_partial.clicked.connect(self.PB_no_partial_clicked)
    
    def PB_no_partial_clicked(self):
        print("PB_no_partial_clicked")

class UiOwnPartial(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Own partial")

        self.setObjectName(u"own_partial")
        self.centralwidget = QWidget(self)
        self.centralwidget.setObjectName(u"centralwidget")

        self.PB_own_partial = QPushButton(self.centralwidget)
        self.PB_own_partial.setObjectName(u"PB_own_partial")
        self.PB_own_partial.clicked.connect(partial(self.PB_own_partial_clicked, "aaa"))
    
    def PB_own_partial_clicked(self, text:str):
        if text == "aaa":
            print("aaa")
        else:
            print("bbb")

class UiOthersPartial(QMainWindow):
    def __init__(self, others):
        super().__init__()
        self.setWindowTitle("Other's partial")

        self.setObjectName(u"others_partial")
        self.centralwidget = QWidget(self)
        self.centralwidget.setObjectName(u"centralwidget")

        self.others = others

        self.PB_others_partial = QPushButton(self.centralwidget)
        self.PB_others_partial.setObjectName(u"PB_others_partial")
        self.PB_others_partial.clicked.connect(partial(self.others.PB_own_partial_clicked, "bbb"))


if __name__ == "__main__":
    test = UiTest()
    test.show()

Now, run this code, and interesting things happen:

when I click "no partial" pushbutton for many times, only one "no partial" window would be opened;
when I click "own partial" pushbutton for many times, each time one new "own partial" window would be opened;
when I click "other's partial" pushbutton for many times, only one "other's partial" window would be opened.

So we can conclude: in PySide6, if one QMainWindow object has a pushbutton connected to it's own partial function, it could be opened many times, or it could be opened only once event though it be opened by user many times.
I wonder why this could happen? what makes it different? Why "own partial" is different from "othere's partial"?
I didn't test it on other python QT versions like PyQt5/6 or PySide2, but I guess same thing would happen. Anyone has PyQt5/6 or PySide2 could have a try.
My environment:
windows 10
python 3.10.1
PySide6 6.2.2.1

Answer: My guess is that when you call partial, it creates a reference to each of its called arguments which will stay around until the new partial object is dereferenced.
Thus, when you run partial(self.PB_own_partial_clicked, "aaa"), you end up creating a reference to self which then results in a situation where the UiOwnPartial instance ends up with a reference to partial which holds a reference to itself.
Because of that, even when you overwrite the self.own_partial property in the UiTest window, the UiOwnPartial window can continue to exist since Python detects that a reference to the window still exists in the partial object.