目录
前言:
本文可能对于使用Qt中connect的作用更大,对于python一般编程可能有些鸡肋,望轻喷
一、问题——lambda的坑
关于问题,先上示例代码
(该示例与connect无关,与connect相关代码见后文,目录的第四部分)
if __name__ == '__main__':
def plus1(num):
return num + 1
func = [] # 存放10个lambda函数的列表
for i in range(10):
func.append(lambda : plus1(i))
print(i)
result_1 = []
# 循环调用10个lambda函数
for i in range(10):
result_1.append( func[i]() )
result_2 = []
# 循环调用_2
for k in range(10):
result_2.append( func[k]() )
print(result_1)
print(result_2)
你觉得结果会是什么呢?报错?有可能
后面的循环呢?都是 1~10 ?
真正的结果如下!
9
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[10, 10, 10, 10, 10, 10, 10, 10, 10, 10]
二、原因&解释
由上面的示例代码,我们可以看到2个奇怪的现象!
1. 临时变量不再临时!!!
按道理临时变量 ,在for循环外,就不应该能够被调用!
实际上,我的编辑器Pycharm也是这么提醒我的——“变量可能未被定义”
这可能与编译器有关,不同的python版本可能不一样。
我个人认为,这主要是为了方便,或挽救一些不规范代码的错误,
但这种以容忍不规范编码的代价可能往往比带来的便利更大!!!会带来更多隐藏的bug,或编码的不便。
至少我觉得临时变量这一点就应该完全遵循临时性!
如果完全遵循临时性的假设成立,
且注释掉 print(i) ,代码的运行结果应该是
[10, 10, 10, 10, 10, 10, 10, 10, 10, 10]
[10, 10, 10, 10, 10, 10, 10, 10, 10, 10]
那么,另一个问题来了,为什么不是 1~10 ?而全是10嘞!?
2. 函数参数是引用传递!!(与lambda有关)
一般的,在函数传递参数时,
对于( int,float,string,元组 )等值固定的类型都是值传递
对于( 列表,字典,set)等可变类型的参数都是引用传递
但是!
lambda就是朵靓丽的奇葩!
(差点没把我折腾死,我开始还以为是connect的问题,改了好久没有用)
一句话!在lambda面前,统统都是引用传递!!!
那么,就可以理解了,所有列表中存储的函数,其传递的参数的值都是 经过迭代器range()后,最终的迭代值
三、解决办法
办法倒是简单,包装在普通函数里面呀!
if __name__ == '__main__':
def plus1(num):
return lambda : num + 1
func = [] # 存放10个函数的列表
for i in range(10):
func.append(plus1(i))
result_1 = []
# 循环调用10个函数
for i in range(10):
result_1.append( func[i]() )
result_2 = []
# 循环调用_2
for k in range(10):
result_2.append( func[k]() )
print(result_1)
print(result_2)
这样,每次传递的 ,都是值传递了,
虽然lambda中的num仍然是引用传递,但每次都是新的num了。
四、槽函数的2种调用方式
上面的例子,可能会显得这种lambda的用法,有些无厘头,且鸡肋
但其实对于Qt中connect的第1种槽函数的调用方式,是挺有用的
1. 槽函数调用方式一(使用lambda)
还是先上代码(错误版)
import sys
from PySide2.QtWidgets import *
from PySide2.QtCore import *
class widget_test(QWidget):
def __init__(self):
super(widget_test, self).__init__()
'''
窗口部件生成
'''
l_whole = QHBoxLayout()
l_1 = QVBoxLayout()
pbt_1 = QPushButton(str(1))
pbt_1.setObjectName("pbt_1")
pbt_2 = QPushButton(str(2))
pbt_2.setObjectName("pbt_2")
l_1.addWidget(pbt_1)
l_1.addWidget(pbt_2)
l_2 = QVBoxLayout()
spin_1 = QSpinBox()
spin_1.setObjectName("spin_1")
spin_2 = QSpinBox()
spin_2.setObjectName("spin_2")
l_2.addWidget(spin_1)
l_2.addWidget(spin_2)
l_whole.addLayout(l_1)
l_whole.addLayout(l_2)
self.setLayout(l_whole)
self.connect_establish()
def connect_establish(self):
'''
遍历所有QPushButton
'''
for i in range(1, 3):
ptn = self.findChild(QPushButton, "pbt_" + str(i))
ptn.clicked.connect(lambda : self.ptn_clicked(i))
def ptn_clicked(self, num):
'''
QPushButton点击信号对应的槽函数
对应的QSpinBox值增加1
'''
print(num)
spin = self.findChild(QSpinBox, "spin_" + str(num))
spin.setValue(spin.value() + 1)
if __name__ == '__main__':
QCoreApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
app = QApplication([])
main_widget = widget_test()
main_widget.show()
sys.exit(app.exec_())
运行效果是这样的
函数ptn_clicked()接收到的num全为引用的
根据第三部分的解决办法更改为(只显示connect_establish()函数部分)
def connect_establish(self):
'''
遍历所有QPushButton
'''
def establish(num):
ptn = self.findChild(QPushButton, "pbt_" + str(num))
ptn.clicked.connect(lambda: self.ptn_clicked(num))
for i in range(1, 3):
establish(i)
运行效果为
奈斯!成功!!!
2. 槽函数调用方式二(非lambda)
以下面4种控件进行测试讲解,代码如下
import sys
from PySide2.QtWidgets import *
from PySide2.QtCore import *
class widget_test(QWidget):
def __init__(self):
super(widget_test, self).__init__()
'''
窗口部件生成,并建立连接
'''
l_whole = QHBoxLayout()
# TreeWidget
tree = QTreeWidget()
tree.setColumnCount(2)
tree.setHeaderLabels(['Key', 'Value'])
tree.setColumnWidth(0, 120) # 第一列列宽设为120
root = QTreeWidgetItem(tree)
root.setText(0, '根节点')
child = QTreeWidgetItem(root)
child.setText(0, '子节点')
child.setText(1, '子节点的数据')
child.setCheckState(0, Qt.Unchecked) # 设置子节点,开启复选框状态
root.setExpanded(True)
tree.itemChanged.connect(self.treeItem_changed)
tree.itemClicked.connect(self.treeItem_clicked)
# PushButton
pbt = QPushButton("pushButton")
pbt.setObjectName("pbt")
pbt.clicked.connect(self.else_clicked)
# CheckBox
chb = QCheckBox("checkBox")
chb.setObjectName("chb")
chb.clicked.connect(self.else_clicked)
# SpinBox
spin = QSpinBox()
spin.setObjectName("spin")
spin.valueChanged.connect(self.spinBox_changed)
l_whole.addWidget(tree)
l_whole.addWidget(pbt)
l_whole.addWidget(chb)
l_whole.addWidget(spin)
self.setLayout(l_whole)
def treeItem_clicked(self, item, column):
print("\nTree Item Clicked")
print("The Column : ", column)
print("Item Text : ", item.text(column))
def treeItem_changed(self, item, column):
print("\nTree Item Changed")
print("The Column : ", column)
print("Item Text : ", item.text(column))
def spinBox_changed(self, signal):
print("\nSpin Box Changed")
print(signal)
def else_clicked(self, signal):
print("\nELSE Clicked")
print(signal)
if __name__ == '__main__':
QCoreApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
app = QApplication([])
main_widget = widget_test()
main_widget.show()
sys.exit(app.exec_())
运行效果如下
TreeWidget
传输的信号有2个:
item本身,可以进行QTreeWidgetItem的一系列操作,如上述代码中的读取节点指定column的文本信息item.text(column)
column,点击处所在的列数
常用的触发分2种:
itemChanged,节点任何属性更改后触发
itemClicked,节点被点击后触发
通过更改节点复选框的CheckState(选中状态),可以看到同时触发了itemChanged和itemClicked,且两者的先后触发顺序是不可控的,同时使用时需多加小心,或者尽量避免同时使用
其他3个控件
这3种控件都只传递1个信号
pushButton的传输信号永远只有False,也就是毫无用处
checkBox会传输是否选中,True或False。但是不会告诉你是哪个checkBox
spinBox
对应的触发函数是valueChanged,至于点击、悬停等触发函数我没有找到,可能需要通过其他复杂一些的方式进行操作
不过传输的信号就很明显了,就是当前的数值,无论是通过按钮还是直接输入,只要改变了数值,就会触发并发送当前Box的数值
至于其他控件,uu们就自己去尝试吧
其他注意事项
- 对于第2种非lambda的无参调用方法,接收的信号可少不可多,甚至槽函数一个信号也不接受都是可以的,但一旦多了就会报错
- 对于checkBox,若想要得知是哪个checkBox,那就使用lambda,传递有关名字的参数,然后在Widget内使用findChild()函数
- 养成良好的命名习惯
- 每个控件必命名,只要一诞生就给它命名
- 通过数字编号,或通过字典、列表等的值命名,这样都可以便于循环生成或处理
- 不要混淆2种槽函数的调用方式
- 要么用,.connect( lambda : func(arg_1, ...) )
- 要么用,.connect( func )
- 就是不能,.connect( func(arg_1, ...) ),有括号无参数也不行,这种调用不会报错,只会在程序执行到这里时,就直接运行func()函数了,且根本没有建立任何connect
最后,文章中如果有什么不对的地方,欢迎各位批评指正。如果这篇文章对你有帮助,希望能动动你灵动的小手点个赞呀!目前发了3篇文章一个赞都还没有,呜呜呜......
各位高考学子加油鸭!!!