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

news2024/11/18 12:24:27

本文目录

  • 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转进到函数参数传递

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 SimpleNamespace

from PyQt5.QtWidgets import QApplication, QInputDialog, QMainWindow, QPushButton, QLabel, QWidget, QVBoxLayout


def 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 = val
    else:
        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 = None

    wm.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 = func
    newfunc.args = args
    newfunc.keywords = keywords
    return newfunc

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

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

Summary

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/492604.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

HTML购物车示例(勾选、删除、添加和结算功能)

以下是一个简单的HTML购物车示例,包含勾选、删除、添加和结算功能。结算功能使用PHP实现,可以获取选中商品的ID。 以下是一个简单的HTML购物车示例,包含勾选、删除、添加和结算功能。结算功能使用PHP实现,可以获取选中商品的ID以下…

Linux下安装Redis

下载 方式一:官网下载稳定版本,然后FTP上传至服务器 https://download.redis.io/releases/ 方式二:服务器内使用wget下载(想下其它版本可参考上图,更改命令后缀版本即可) wget http://download.redis.i…

2.基础篇

目录 一、描述软件测试的生命周期(软件测试的流程) 二、如何描述一个bug 三、bug的级别(粗略划分) 四、bug的生命周期 五、因为一个bug和开发人员产生争执怎么办 六、如何设置弱网? 一、描述软件测试的生命周期&a…

Flex弹性布局

文章目录 1. 开启Flex布局2. 应用于flex container的css属性flex-directionjustify-contentalign-itemsflex-wrapflex-flowalign-content 3. 应用于flex items的css属性orderflex-growflex-shrinkflex-basis(了解)align-selfflex 1. 开启Flex布局 flex c…

校招推荐学习java开发还是大数据开发

这两个方向其实都是不错的方向,java虽然卷,但是技能在手也不怕。大数据的发展前景也是不容小觑的。关键就在于你未来想发展的方向以及个人的兴趣 首先可以肯定的是,市场上终归是需要Java人才的,但是总会有人来问,Java…

对偶问题和KKT条件

KKT条件 对于不等式约束优化问题 min ⁡ f ( x ) s . t . g ( x ) ≤ 0 \min\quad f(x)\\ {\rm s.t.}\quad g(x)\leq 0 minf(x)s.t.g(x)≤0 拉格朗日函数为 L ( x , λ ) f ( x ) λ g ( x ) L(x,\lambda)f(x)\lambda g(x) L(x,λ)f(x)λg(x) 。 KKT条件包括 拉格朗日函…

分享5款轻量级的Win10神器,错过你会后悔的

今天我要为大家推荐五款小众而且小体积的WIN10小工具,它们可以让你的电脑使用更加方便和高效,而且不占用太多的空间和资源,非常适合轻量级的办公和娱乐。 1.窗口管理工具——TileIconifier TileIconifier可以将窗口最小化到托盘区域,从而更…

在Android应用中集成使用traceroute工具

背景知识 traceroute是一个常用于Linux系统的网络工具,它可显示数据包在IP网络中所经过路由的IP地址,理想状态下可探测本机和目标地址之间的所有路由节点。 其他操作系统中也有类似的替代品,实现都大同小异。一般用法如下: 终端…

【TCP为什么需要粘包和拆包】

如今,大半个互联网都建立在 TCP 协议之上,我们使用的 HTTP 协议、消息队列、存储、缓存,都需要用到 TCP 协议——这是因为 TCP 协议提供了可靠性。简单来说,可靠性就是让数据无损送达。但若是考虑到成本,就会变得非常复…

一文带你理解SpringBean

Bean定义 ​ Bean作为Spring框架面试中不可或缺的概念,其本质上是指代任何被Spring加载生成出来的对象。(本质上区别于Java Bean,Java Bean是对于Java类的一种规范定义。)Spring Bean代表着Spring中最小的执行单位,其…

如何用ApiFox自动生成接口文档?没有比这更详细的教程了

目录 前言 第一步:安装 Apifox IDEA 插件(Apifox Helper) 第二步:配置 Apifox 访问令牌 和项目 ID 第三步:自动生成文档! 第四步:去 Apifox 项目中查看自动生成的文档 Apifox 更多好用的功能…

Addictive Multiplicative in NN

特征交叉是特征工程中的重要环节,在以表格型(或结构化)数据为输入的建模中起到了很关键的作用。 特征交互的作用,一是尽可能挖掘对目标有效的模式、特征,二是具有较好的可解释性,三是能够将对数据的洞见引…

一文教会你如何重装Windows10系统【过程+图解+说明】

前言 申请了一台台式机电脑,操作系统是windows11的,要windows10的系统。电脑不能连网,身为程序员,我竟然想着别人远程帮我安装,可恶呐。之前也没重装过系统。第一次重装遇到了一些坑。我甚至在拼夕夕上花了几块钱买个镜…

python-使用Qchart总结5-使用信号槽绘制动态曲线图

python-使用Qchart总结3-绘制曲线图在这篇文章基础上,来改造一下,绘制一下动态曲线图吧 一、明确需求 ①点击按钮,开始动态加载曲线,细节:一个一个点加载出来 二、实现 ①在UI上添加按钮,打开原先的untitled.ui文件…

【Linux】浅谈eloop机制

目录 1.eloop 机制 2.eloop结构体 2.1.eloop_data结构体 2.2 Socket事件结构体 2.3 Timeout事件结构体 2.4 Signal事件结构体 3.eloop_init 4.eloop_run 4.1 signal事件 4.2 socket事件 4.3 timeout事件 1.eloop 机制 主线程中启动事件监听机制,对不同的…

【Python入门】字符串的扩展

前言 📕作者简介:热爱跑步的恒川,致力于C/C、Java、Python等多编程语言,热爱跑步,喜爱音乐的一位博主。 📗本文收录于Python零基础入门系列,本专栏主要内容为Python基础语法、判断、循环语句、函…

Nginx介绍及安装

简介 Nginx 是一个高性能的 HTTP 和反向代理服务器。它最初由 Nigel Cook 开发,旨在解决 Apache 服务器在高并发环境下性能瓶颈的问题。Nginx 具有占用资源少、处理能力强等优点,在互联网应用中广泛应用于静态资源服务、反向代理、负载均衡、HTTP缓存、…

2023年web前端开发之JavaScript进阶(一)

接上篇博客进行学习,通俗易懂,详细 博客地址: 2023年web前端开发之JavaScript基础(五)基础完结_努力的小周同学的博客-CSDN博客 学习内容 学习 作用域、变量提升、 闭包等语言特征,加深对 JavaScript 的理解,掌握变量赋值、函数声明的简洁语法&#xff0…

rs485转tcp网关盒子怎么用(rs485协议转以太网tcp/ip)

随着工业自动化技术的不断发展,越来越多的工业设备在使用时需要进行数据通信。其中,RS485通信协议是一种常见的工业通信协议,而TCP/IP协议则是互联网通信的标准协议。为了实现RS485协议与TCP/IP协议之间的通信,可以使用RS485转TCP…

【Java】面试常问知识点(Java基础—2)

Java基础 多线程的状态 新建状态 当用new操作符创建一个线程时, 例如new Thread(r),线程还没有开始运行,此时线程处在新建状态。 当一个线程处于新生状态时,程序还没有开始运行线程中的代码 就绪状态 一个新创建的线程并不自动…