当前位置: 首页 > news >正文

PyQt5桌面应用开发(8):从QInputDialog转进到函数参数传递

本文目录

  • PyQt5桌面应用系列
  • How old are you, Dialog?
  • QInputDialog minimalist
  • why not lambda
  • and how partial works
  • Summary

PyQt5桌面应用系列

  • PyQt5桌面应用开发(1):需求分析
  • PyQt5桌面应用开发(2):事件循环
  • PyQt5桌面应用开发(3):并行设计
  • PyQt5桌面应用开发(4):界面设计
  • PyQt5桌面应用开发(5):对话框
  • PyQt5桌面应用开发(6):文件对话框
  • PyQt5桌面应用开发(7):文本编辑+语法高亮与行号
  • PyQt5桌面应用开发(8):从QInputDialog转进到函数参数传递
  • PyQt5桌面应用开发(9):经典布局QMainWindow
  • PyQt5桌面应用开发(10):界面布局基本支持
  • PyQt5桌面应用开发(11):摸鱼也要讲基本法,两个字,16

How old are you, Dialog?

兜兜转转,觉得Dialog这个话题还有一点点可以写一篇。那就是QIputDialog。

我本人是不知道为啥要有这个类的。

因为我确实没感觉到有太大的需要,UI提供了在位的输入元素,比如QLineEdit、Spinner、Slider之类,直接输入就行,跳出一个对话框,让用户输入一个简单的文本、数字、浮点类型,到底有什么必要。

从用户体验上看,惊喜是应该尽可能少出现的,比如弹出一个对话框。我看到清华出版的那本《PyQt从入门到精通》里面关于QInputDialog的例子,点击一个文本框,弹出一个对话框,输入一个文本,点Ok关闭对话框,文本加入文本框。实在是叹为观止,惊为天人……

那么为什么我也写一个篇呢?有好几个理由。

  1. 我想找一个用它的理由;
  2. 我实在搬砖搬到“让用户体验毁灭吧!”
  3. 居然觉得这是一个搞清楚闭包的机会……【!】

QInputDialog minimalist

下面我们做一个最小化的QInputDialog的例子。

QInputDialog
其实还不错!

报表:

  • 用户选择的整数显示在一个QLabel上;
  • 用户选的数据可以打印出来

数据:

  • 一个整数, ∈ [ 0 , 100 ] \in [0, 100] [0,100]
  • 通过QInputDialog.getInt获得

所以这个最小化的版本里面没有按照对象继承的方法,基本的流程采用面向过程的方式编写。

import sys
from functools import partial
from types import SimpleNamespacefrom PyQt5.QtWidgets import QApplication, QInputDialog, QMainWindow, QPushButton, QLabel, QWidget, QVBoxLayoutdef global_gis(parent: QWidget, text_output: QLabel, numbers: SimpleNamespace, _: bool):val, flag = QInputDialog.getInt(parent, "Any number in 0,100", "Number", 50, 0, 100)if flag:text_output.setText(f"Number: {val}")numbers.n = valelse:text_output.setText(f"Number not set")if __name__ == '__main__':app = QApplication(sys.argv)wm = QMainWindow()but = QPushButton("Get an Integer")label = QLabel("Number: ")cw = QWidget(wm)box = QVBoxLayout(cw)cw.setLayout(box)ret = SimpleNamespace()but.clicked.connect(partial(global_gis, cw, label, ret))# but.clicked.connect(lambda check: global_gis(cw, label, ret, check))but2 = QPushButton("Current n")but2.clicked.connect(lambda check: print(ret.n))box.addWidget(but)box.addWidget(label)box.addWidget(but2)wm.setCentralWidget(cw)print(id(cw), id(label))# cw = None# label = Nonewm.setMinimumSize(400, 30)wm.show()sys.exit(app.exec_())

这里唯一麻烦事情就是,我不想定义一个类来继承QWidget,出来数据的是一个函数global_gis,这就带来一些麻烦。因为PyQt5的槽函数的形式都是类似于def slot_func(check: bool)->None的形式,那么要完成我们的功能就需要一个函数完成两个功能:

  • 类似于C/C++/C#的引用调用的方式,在参数里把QInputDialog获得的数字传递出来;
  • 还需要把QLabel或者这里的父节点传递进去。

最终,这里选择实现一个完整的函数:def global_gis(parent: QWidget, text_output: QLabel, numbers: SimpleNamespace, _: bool),然后采用partial函数,把这个函数包装成QPushButton.clicked的槽函数的形式,只有一个参数。

why not lambda

上面的程序中注释的解释了为什么不采用lambda,如果按照这种定义方式,如果在程序的下方,cw和label发生了改变,那么就会引起程序直接退出。

but.clicked.connect(lambda check: global_gis(cw, label, ret, check))
# ......
cw = None
label = None

这里的问题就在与Python的函数调用方式。Python的参数传递方式并不是传值,也不是传引用。Python实现了一个非常独特的函数调用。函数的参数实际上是采用赋值的方式传递的,通过赋值,在函数的locals()中保存对应的对象引用。这一点可以通过id()函数来查看函数参数的地址,实参与函数中的形参完全一致。

def compare_ids(x, x_id):print(id(x), " == ", x_id)x = 10  # anything
compare_ids(x, id(x))
print(id(x), " == ", id(10))

把这个值设置成任何值,都会发现,传递进去的对象是一致的。这应该是出于性能的考虑,类似于传递引用的方式。这里就很好的展示了Python中变量名和对象的关系。变量名 ↦ \mapsto 对象,变量名的类型可以随意改变,但是对象有其类型。这两个是不同的。

在函数的内部,访问一个函数参数的值,这没有什么特别的,函数传进来一个对象,函数参数是一个变量名,这个变量名在这个范围内(locals())指向这个对象。

但是在对这个变量名进行赋值的过程时,发生的情况就是这个变量指向的对象发生了改变(这是赋值在Python中的语义)。所以在函数中,改变函数参数的值,并不会改变实际参数(传进来的那个对象)的值。

这是第一个问题:函数参数的传递,Python传的是引用,传进去后绑定到局部变量名。上面的lambda还有另外一个问题。

lambda check: global_gis(cw, label, check)相当于

def _(check: bool):global_gis(cw, label, check)

这里匿名函数的内部访问了两个值:cw和label,这两个值在局部变量中没有,那么这种情况下Python会怎么去找变量名所绑定的对象呢?

  1. 在变量最近的scope中找;
  2. 在包含函数定义的scope中找。

所以,这两个变量就变成了main块中cw和labe。但是,Python变量的绑定是发生在运行时的,所以只有这个函数global_gis实际被调用时,才会找到这两个变量对应的对象,把它们赋值给函数的形式参数。如果,在运行这个函数之前,这两个变量的指向发生了改变,oops!

这就是为什么这里不能这样做。

上面这些分析,给我们通过槽函数来传递参数出来指明了路径。找一个对象,这个对象的内部状态会发生改变,把这个对象传递进函数,在函数内部改变其状态。这里我们用一个简单的SimpleNamespace对象,其实上用list、dict都行。

明白Python的函数变量名和对象的绑定关系,以及函数的参数传引用+赋值的调用方式,我们要做的就很简单,就在调用clicked.connect的当时把相应的对象传递进去,也就是强制对象绑定在当时发生。这就需要用函数编程中的partial出场了。

and how partial works

functools.partial是Python自带的一个返回函数的函数。其实现类似于:

def partial(func, /, *args, **keywords):def newfunc(*fargs, **fkeywords):newkeywords = {**keywords, **fkeywords}return func(*args, *fargs, **newkeywords)newfunc.func = funcnewfunc.args = argsnewfunc.keywords = keywordsreturn newfunc

在执行这个函数时,第一个参数就是被包装的函数,根据调用它输入的参数,它将被调用函数的部分或全部参数固定下来,构成一个新的函数,这个新函数的参数就是哪些没固定的参数。

这里很清楚的是,因为partial是一个函数,那么在connect调用的过程中,它会被实际执行,此时其参数就会被实参绑定。那么在调用这个函数后,变更变量cw和label指向的对象,就再也不影响global_gis内部的变量绑定。

Summary

  1. Python的函数参数传递方式:传引用+赋值调用;
  2. Python的变量绑定时运行时发生的
  3. QInputDialog真的没啥用……
http://www.lryc.cn/news/65946.html

相关文章:

  • 2.0 Vue框架设计的核心要素
  • “智慧赋能 强链塑链”——精细化工行业仓储物流数字化转型探讨
  • 用DG备库做的rman备份恢复一个数据库
  • JAVA中的IO操作有哪些?
  • 10:00面试,10:04就出来了 ,问的实在是太...
  • wangzherongyao PMO
  • Dart语法上
  • SignOff Criteria——POCV(Parametric OCV) introduction
  • Android 内存分析(java/native heap内存、虚拟内存、处理器内存 )
  • 产品思维与工程师思维
  • Android---启动速度优化
  • 使用 Mercury 直接从 Jupyter 构建 Web 程序
  • Python基础(二)
  • 第41讲:Python循环语句中的break-else语法结构
  • 双系统-真机安装ubuntu
  • Android实现向facebook回复消息代码
  • IDEA小技巧-Git的回滚强推代码找回
  • 即时通讯为什么不采用UDP的连接方式呢
  • 二叉树(纲领篇)
  • day41—选择题
  • Vue3 watch 监听对象数组中对象的特定属性
  • 请求策略库alova小记
  • [C++]string的使用
  • Kali Linux 操作系统安装详细步骤——基于 VMware 虚拟机
  • R语言APSIM模型应用及批量模拟实践技术
  • 破解马赛克有多「容易」?
  • 【.NET基础加强第八课--委托】
  • jetcache:阿里这款多级缓存框架一定要掌握
  • 干货 | 如何做一个简单的访谈研究?
  • 4年外包出来,5次面试全挂....