为何PySide2按钮绑定普通函数无效,lambda函数却正常?
PySide2按钮信号绑定异常原因解析
问题场景
基于PySide2开发CTools GUI工具时出现信号绑定异常:点击「Start」按钮无法触发绑定的普通函数输出「Click」,但点击「Cancel」按钮绑定的lambda函数可正常输出「Cancel」。将该普通函数移至CToolsWindow类,并修改绑定路径为self._view._on_start_button_clicked后功能恢复正常,以下是问题原因解析。
原因解析
问题核心是**CTools控制器对象被Python垃圾回收机制销毁**,导致其内部的_on_start_button_clicked函数失效:
- 在初始代码中,
CTools实例仅在main()函数中被创建,但未赋值给任何变量,创建后立即成为无引用对象。当Python垃圾回收器运行时,该实例会被销毁,其绑定的_on_start_button_clicked函数也随之被回收,按钮点击时找不到可执行的函数。 - lambda函数能正常工作,是因为Qt在绑定lambda时会内部持有对该匿名函数的引用,不会被垃圾回收;而
CToolsWindow作为主窗口对象,被Qt应用程序循环持有强引用,不会被销毁,因此移到该类中的函数能稳定触发。
初始异常代码
# ctools.py """CTools is a GUI toolset to interact with your CTERA Environment""" import sys from status import run_status from PySide2.QtWidgets import ( QApplication, QMainWindow, QWidget, QGridLayout, QLineEdit, QPushButton, QVBoxLayout, QToolBar, QLabel, QHBoxLayout ) WINDOW_WIDTH = 600 WINDOW_HEIGHT = 800 OUTPUT_HEIGHT = 200 class CToolsWindow(QMainWindow): """PyCalc's main window (GUI or view).""" def __init__(self): super().__init__() self.setWindowTitle("CTools 3.0") self.setFixedSize(WINDOW_WIDTH, WINDOW_HEIGHT) self.generalLayout = QVBoxLayout() centralWidget = QWidget(self) centralWidget.setLayout(self.generalLayout) self.setCentralWidget(centralWidget) self._createToolBar() self._createRunCMDDisplay() self._createActionButtons() self._createOutput() def _createToolBar(self): tools = QToolBar() tools.addAction("Run CMD", self.close) tools.addAction("Exit", self.close) self.addToolBar(tools) def _createRunCMDDisplay(self): RunCMDLayout = QGridLayout() requiredArgs = QLabel("<h4><b>Required Arguments:</b></h4>") address = QLabel("Address (Portal IP, hostname, or FQDN):") username = QLabel("Portal Admin Username:") password = QLabel("Password") filename = QLabel("Output Filename") self.addressField = QLineEdit() self.usernameField = QLineEdit() self.passwordField = QLineEdit() self.commandField = QLineEdit() RunCMDLayout.addWidget(requiredArgs, 0, 0, 1, 2) RunCMDLayout.addWidget(address, 1, 0) RunCMDLayout.addWidget(username, 1, 1) RunCMDLayout.addWidget(self.addressField, 2, 0) RunCMDLayout.addWidget(self.usernameField, 2, 1) RunCMDLayout.addWidget(password, 3, 0) RunCMDLayout.addWidget(filename, 3, 1) RunCMDLayout.addWidget(self.passwordField, 4, 0) RunCMDLayout.addWidget(self.commandField, 4, 1) self.generalLayout.addLayout(RunCMDLayout) def _createActionButtons(self): actionButtonLayout = QHBoxLayout() self.cancel = QPushButton("Cancel") self.start = QPushButton("Start") actionButtonLayout.addWidget(self.cancel) actionButtonLayout.addWidget(self.start) self.generalLayout.addLayout(actionButtonLayout) def _createOutput(self): self.output = QLineEdit() self.output.setFixedHeight(OUTPUT_HEIGHT) self.output.setReadOnly(True) self.generalLayout.addWidget(self.output) class CTools: """CTools controller class""" def __init__(self, model, view): self._evaluate = model self._view = view self._connectSignalsAndSlots() def _on_start_button_clicked(self): print("Click") def _connectSignalsAndSlots(self): self._view.start.clicked.connect(self._on_start_button_clicked) self._view.cancel.clicked.connect(lambda: print("Cancel")) def model(): return "Hi" def main(): """PyCalc's main function.""" ctoolsApp = QApplication(sys.argv) ctoolsWindow = CToolsWindow() ctoolsWindow.show() CTools(model=model, view=ctoolsWindow) ctoolsApp.exec_() if __name__ == "__main__": main()
修改后正常代码
# ctools.py """CTools is a GUI toolset to interact with your CTERA Environment""" import sys from status import run_status from PySide2.QtWidgets import ( QApplication, QMainWindow, QWidget, QGridLayout, QLineEdit, QPushButton, QVBoxLayout, QToolBar, QLabel, QHBoxLayout ) WINDOW_WIDTH = 600 WINDOW_HEIGHT = 800 OUTPUT_HEIGHT = 200 class CToolsWindow(QMainWindow): """PyCalc's main window (GUI or view).""" def __init__(self): super().__init__() self.setWindowTitle("CTools 3.0") self.setFixedSize(WINDOW_WIDTH, WINDOW_HEIGHT) self.generalLayout = QVBoxLayout() centralWidget = QWidget(self) centralWidget.setLayout(self.generalLayout) self.setCentralWidget(centralWidget) self._createToolBar() self._createRunCMDDisplay() self._createActionButtons() self._createOutput() def _createToolBar(self): tools = QToolBar() tools.addAction("Run CMD", self.close) tools.addAction("Exit", self.close) self.addToolBar(tools) def _createRunCMDDisplay(self): RunCMDLayout = QGridLayout() requiredArgs = QLabel("<h4><b>Required Arguments:</b></h4>") address = QLabel("Address (Portal IP, hostname, or FQDN):") username = QLabel("Portal Admin Username:") password = QLabel("Password") filename = QLabel("Output Filename") self.addressField = QLineEdit() self.usernameField = QLineEdit() self.passwordField = QLineEdit() self.commandField = QLineEdit() RunCMDLayout.addWidget(requiredArgs, 0, 0, 1, 2) RunCMDLayout.addWidget(address, 1, 0) RunCMDLayout.addWidget(username, 1, 1) RunCMDLayout.addWidget(self.addressField, 2, 0) RunCMDLayout.addWidget(self.usernameField, 2, 1) RunCMDLayout.addWidget(password, 3, 0) RunCMDLayout.addWidget(filename, 3, 1) RunCMDLayout.addWidget(self.passwordField, 4, 0) RunCMDLayout.addWidget(self.commandField, 4, 1) self.generalLayout.addLayout(RunCMDLayout) def _createActionButtons(self): actionButtonLayout = QHBoxLayout() self.cancel = QPushButton("Cancel") self.start = QPushButton("Start") actionButtonLayout.addWidget(self.cancel) actionButtonLayout.addWidget(self.start) self.generalLayout.addLayout(actionButtonLayout) def _createOutput(self): self.output = QLineEdit() self.output.setFixedHeight(OUTPUT_HEIGHT) self.output.setReadOnly(True) self.generalLayout.addWidget(self.output) def _on_start_button_clicked(self): print("Click") class CTools: """CTools controller class""" def __init__(self, model, view): self._evaluate = model self._view = view self._connectSignalsAndSlots() def _connectSignalsAndSlots(self): self._view.start.clicked.connect(self._view._on_start_button_clicked) self._view.cancel.clicked.connect(lambda: print("Cancel")) def model(): return "Hi" def main(): """PyCalc's main function.""" ctoolsApp = QApplication(sys.argv) ctoolsWindow = CToolsWindow() ctoolsWindow.show() CTools(model=model, view=ctoolsWindow) ctoolsApp.exec_() if __name__ == "__main__": main()
内容的提问来源于stack exchange,提问作者Lake Pry
相关产品推荐
相关产品推荐

