PySide6中QMainWindow关联自身partial函数可多次打开的原因探究
测试代码
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()
运行现象
- 多次点击“no partial”按钮,仅打开一个“No partial”窗口;
- 多次点击“own partial”按钮,每次都会打开新的“Own partial”窗口;
- 多次点击“other's partial”按钮,仅打开一个“Other's partial”窗口。
原因分析
核心差异在于Qt对象的引用计数和生命周期管理,以及partial带来的隐式引用关系:
No partial窗口(仅打开一个)
每次点击按钮时,UiTest的self.no_partial会被重新赋值为新的UiNoPartial对象,旧对象失去了UiTest的引用。Qt中,无父对象且无其他强引用的QMainWindow,关闭后会触发对象销毁。所以每次点击都是替换成新窗口,旧窗口被销毁,看起来只能打开一个。Own partial窗口(可多次打开)
UiOwnPartial的按钮用partial(self.PB_own_partial_clicked, "aaa")绑定自身方法时,partial对象会持有对UiOwnPartial实例的强引用,同时Qt的信号槽机制又会持有这个partial对象的引用。当关闭窗口时,因为这两层引用的存在,对象的引用计数不为0,无法被Python垃圾回收,Qt也不会主动销毁它。因此每次点击按钮都会创建新的UiOwnPartial实例,旧实例虽窗口关闭但仍驻留内存,所以能打开多个新窗口。Other's partial窗口(仅打开一个)
UiOthersPartial的按钮绑定的是partial(self.others.PB_own_partial_clicked, "bbb"),这里partial持有的是UiTest中self.own_partial的引用,而非UiOthersPartial自身的引用。关闭窗口后,UiTest的self.others_partial会被下次点击覆盖,旧窗口没有额外强引用,会被正常销毁。每次点击只是替换成新窗口,所以看起来只能打开一个。
内容的提问来源于stack exchange,提问作者Chuang Men

