You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PySide6中QMainWindow关联自身partial函数可多次打开的原因探究

问题:PySide6中关联自身partial的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带来的隐式引用关系:

  1. No partial窗口(仅打开一个)
    每次点击按钮时,UiTest的self.no_partial会被重新赋值为新的UiNoPartial对象,旧对象失去了UiTest的引用。Qt中,无父对象且无其他强引用的QMainWindow,关闭后会触发对象销毁。所以每次点击都是替换成新窗口,旧窗口被销毁,看起来只能打开一个。

  2. Own partial窗口(可多次打开)
    UiOwnPartial的按钮用partial(self.PB_own_partial_clicked, "aaa")绑定自身方法时,partial对象会持有对UiOwnPartial实例的强引用,同时Qt的信号槽机制又会持有这个partial对象的引用。当关闭窗口时,因为这两层引用的存在,对象的引用计数不为0,无法被Python垃圾回收,Qt也不会主动销毁它。因此每次点击按钮都会创建新的UiOwnPartial实例,旧实例虽窗口关闭但仍驻留内存,所以能打开多个新窗口。

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 12:57:04