【Qt】【Python】【lambda】关于connect时使用lambda的坑(无法值传递),和槽函数的2种调用方式以及信号传递问题

目录

一、问题——lambda的坑

二、原因&解释

1. 临时变量不再临时!!!

2. 函数参数是引用传递!!(与lambda有关)

三、解决办法

四、槽函数的2种调用方式

1. 槽函数调用方式一(使用lambda)

2. 槽函数调用方式二(非lambda)


前言:

本文可能对于使用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. 临时变量不再临时!!!

按道理临时变量 i,在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面前,统统都是引用传递!!!

那么,就可以理解了,所有列表中存储的函数,其传递的参数的值都是 i 经过迭代器range()后,最终的迭代值 i=9

三、解决办法

办法倒是简单,包装在普通函数里面呀!

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)

这样,每次传递的 i,都是值传递了,

虽然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全为引用i=2

根据第三部分的解决办法更改为(只显示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们就自己去尝试吧

其他注意事项

  1. 对于第2种非lambda的无参调用方法,接收的信号可少不可多,甚至槽函数一个信号也不接受都是可以的,但一旦多了就会报错
  2. 对于checkBox,若想要得知是哪个checkBox,那就使用lambda,传递有关名字的参数,然后在Widget内使用findChild()函数
  3. 养成良好的命名习惯
    • 每个控件必命名,只要一诞生就给它命名
    • 通过数字编号,或通过字典、列表等的值命名,这样都可以便于循环生成或处理
  4. 不要混淆2种槽函数的调用方式
    • 要么用,.connect( lambda : func(arg_1, ...) )
    • 要么用,.connect( func )
    • 就是不能.connect( func(arg_1, ...) ),有括号无参数也不行,这种调用不会报错,只会在程序执行到这里时,就直接运行func()函数了,且根本没有建立任何connect

最后,文章中如果有什么不对的地方,欢迎各位批评指正。如果这篇文章对你有帮助,希望能动动你灵动的小手点个赞呀!目前发了3篇文章一个赞都还没有,呜呜呜......

各位高考学子加油鸭!!!

  • 7
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值