我正在开发一个Python应用程序,该应用程序从一个包含大量来源记录的大文件中绘制数据。我试图给用户的一个选项是,如果需要的话,只为这些来源的一个子集绘图。我首先读取文件,找出有多少唯一的东西,然后为每个文件创建一个QCheckBox(),以其源命名(每个源都有一个唯一的名称)。在这种特殊情况下,数据文件被解析到一个巨大的字典中,其中的键是唯一的源。我想为每个复选框连接到stateChange()事件,然后在取消选中该复选框时禁用该源的绘图。在这种情况下,当复选框被选中/取消选中时,它将从源列表中添加/删除源。我遇到的问题是,我所有的复选框最终都连接到了列表中的最终源。
最初,创建的窗口看起来是正确的,每个按钮都有适当的名称。每次按下按钮时,btnstate()应该只打印与该按钮相关的文本。如果可以显式定义每个按钮,则该方法有效,如示例中的单选按钮所示。如果单击其中一个,您将打印出按钮的正确名称,但当取消选中/重新选中任何复选框时,btnstate将打印"test4"。
我做错了什么?
以下是代码(源更改为伪值):
import sys
from PyQt4.QtGui import *
def btnstate(b):
print b.text()
def main():
app = QApplication([])
widget = QWidget()
layout = QVBoxLayout()
widget.setLayout(layout)
radio_layout = QHBoxLayout()
checkbox_layout = QHBoxLayout()
#setup radio buttons for config pop-up window
r1 = QRadioButton("Page Count")
r2 = QRadioButton("Date")
r1.toggled.connect(lambda:btnstate(r1))
r2.toggled.connect(lambda:btnstate(r2))
radio_layout.addWidget(r1)
radio_layout.addWidget(r2)
cbs = []
for idx, serial in enumerate(["test1", "test2", "test3", "test4"]):
temp = QCheckBox(serial)
temp.setText(serial)
temp.setChecked(True)
checkbox_layout.addWidget(temp)
temp.stateChanged.connect(lambda:btnstate(temp))
cbs.append(temp)
layout.addLayout(radio_layout)
layout.addLayout(checkbox_layout)
widget.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
我认为发生这种情况的原因与Python如何绑定和取消绑定循环中的引用有关。因为temp
在每次迭代中都被重新定义,所以slot也在更新,这样它就可以有效地为每个按钮调用相同的lambda。这有点像波浪形,因为我对Python引用细节的理解并不是很深入。但我知道,Python与Qt的绑定在Python的引用和垃圾收集方面有很多问题,例如在删除小部件时,因为Qt对象层次结构不能完全与Python配合使用。
无论如何,更实际的是,有一个非常简单的解决方案。可以使用functools.partial
方法将分部函数定义为slot,而不是lambda。将按钮绑定为第一个对象,使按钮状态(作为信号参数发出)未绑定。像这样:
import functools
def btnstate(button, state):
print button.text()
然后在循环中:
for idx, serial in enumerate(['test1', 'test2', 'test3', 'test4']):
temp = QCheckBox(serial)
checkbox_layout.addWidget(temp)
temp.stateChanged.connect(functools.partial(btnstate, serial))
运行此操作,我现在可以在选中/取消选中每个框时打印正确的标签。
编辑:
请参阅这篇文章,了解Python的引用计数与Qt的对象层次结构以奇怪的方式交互的另一个例子。