提示:本博客作为学习笔记,有错误的地方希望指正,此文可能会比较长,作为学习笔记的积累,希望对来着有帮助。
 绪论:笔者这里使用的是QTCreator和Python来实现一个简单的串口上位机的开发的简单过程,使用到Python,之前记录的Qt 使用C++ 写上位机也记录一篇文章,大家感兴趣的话可以看看。从零开始编写一个上位机(串口助手)QT Creator + C++,这里我使用Python写上位机主要的原因就是Python强大的数据抓取能力以及数据处理能力,我们可以使用Python做上位机作为自动化测试工具,采集之后的数据整合都是 非常强大的,因为Python和C++都是高级语言,所以和前面叙述使用C++写上位机的流程有许多相似之处。唯一的差别就是想要使用QT 写上位机的话默认语言就是C++ 的,我们需要一个工具将QT设计的界面翻译成Python,后续就直接可以使用Python 引用我们使用QT设计的界面元素,对界面元素的数据处理以及数据交互实现控制;将C++转化成Python 目前主流的有PyQT和Pyside,至于PyQT5、PyQT6、Pyside2、Pyside6只是版本不同而已,至于两者的主要差别PyQT5是第三方机构最初开始去做的,起步比较早资料比较完善,但是会收费的,Pyside 主要是Python 他们自己去维护的,相当于Python的亲儿子PyQT相当于干儿子,所以目前大家都比较看好Pyside,我这里是使用的Pyside2 其实目前Pyside 和PyQT大部分是兼容的,你只需要将PyQT的名字改为Pyside就可以了。

文章目录

  • 一、Python知识
    • 1、初识Python
    • 2、Python输入输出控制
      • 2.1、Python的输入input
      • 2.2、Python的输出控制print
    • 3、Python的数据结构
      • 3.1、数字
      • 3.2、字符串
      • 3.3、数据结构之通用序列操作
        • 3.3.1、索引
        • 3.3.2、切片
        • 3.3.3、序列相加
        • 3.3.3、序列重复
        • 3.3.4、成员资格
        • 3.3.4、长度、最值、求和
      • 3.4、列表
        • 3.4.1、新增元素
        • 3.4.2、删除元素
        • 3.4.3、查找元素
      • 3.5、元组
      • 3.6、字典
      • 3.7、集合
      • 3.8、推导公式
    • 4、Python 的流程控制
      • 4.1 if判断
        • 4.1.1、单独if 语句
        • 4.1.2、else语句
        • 4.1.3、elif语句
      • 4.2 循环
        • 4.2.1、while循环
        • 4.2.2、for循环
        • 4.2.3、break、continue、和pass值
        • 4.2.4、循环中的else
      • 4.3、运算符
        • 4.3.1、算术运算符
        • 4.3.2、比较运算符
        • 4.3.3、赋值运算符
        • 4.3.4、位运算符
        • 4.3.4、逻辑运算符
    • 5、函数
      • 5.1、函数参数
      • 5.2、函数返回值
      • 5.3、变量的作用域
      • 5.4、Lambda表达式
    • 6、面向对象
      • 6.1、介绍
      • 6.2、定义语法
      • 6.3、类的使用
      • 6.4、类的构造方法
      • 6.5、类的属性
      • 6.5、类的方法
      • 6.5、私有属性
      • 6.5、私有方法
      • 6.6、继承和多态
        • 6.6.1、继承
        • 6.6.2、多态
        • 6.6.3、类知识拓展
      • 7、模块
        • 7.1、模块介绍
        • 7.2、__name__变量
        • 7.2、dir函数
        • 7.3、使用模块
        • 7.4、错误和异常
          • 7.4.1、错误
          • 7.4.2、异常
          • 7.4.3、异常处理
          • 7.4.4、finally子句
      • 8、多线程和进程
        • 8.1.进程基本概念
        • 8.2.线程基本概念
        • 8.3.python线程模块
  • 二、QT知识
    • 2.1、初识QT
    • 2.2、Python下怎么结合QT一起开发设计
      • 2.2.1、安装Pyside2 包
      • 2.2.2、在pycharm中添加插件
        • 2.2.2.1 配置pyside2-designer外部工具
        • 2.2.2.2 配置pyside2-uic外部工具
        • 2.2.2.3 配置Pyside2-rcc外部工具
    • 2.3、认识pyside2-designer界面
    • 2.4、使用QT界面设计师设计串口助手界面
      • 2.4.1、数据接收区设计
      • 2.4.2、串口信息配置区
      • 2.4.3、串口控制区
      • 2.4.4、MainWindow的参数设置
  • 三、Python实现串口助手通讯逻辑
    • 3.1、将QT 界面设计师的文件转换成python文件
    • 3.2、python 串口逻辑实现
      • 3.2.1、QT中定时器
      • 3.2.2、信号与槽
      • 3.2.3、通讯交互源码
      • 3.2.4、python代码打包
      • 3.2.5、源码地址

一、Python知识

1、初识Python

  对于Python而言,和C++一样的都是高级语言,都是面向对象的语言。

2、Python输入输出控制

  Python中使用input和print实现输入输出控制,输入input默认类型是str类型的,需要其他类型需要做数据类型的转换。

2.1、Python的输入input

  输入input() 注意 输入默认式 str类型的,需要其他类型需要类型转换

name = input("请输入你的名字>>")print(name)password = input("请输入你的密码>>")print(password)print(type(password))   # <class 'str'>

2.2、Python的输出控制print

  Python的输出控制使用的是print来实现输出显示的,和C一样的可以使用控制输出符实现对输出格式的控制。Python的输出print的输出控制有许多种类别。直接使用占位符实现输出、使用xxx.format形式、使用f”{}”的形式。速度对比f’{}‘>str.format()(推荐使用)>%(占位符),值得注意的是下面使用到字符串的显示,字符串通常可以 ”(字符串)“、’(字符串)‘、’‘’(字符串)‘’’三种形式显示字符串,最后这种可以显示多行换行形式的字符串。
2.2.1、使用占位符输出% 占位符有%s %d %f

name = input("name:")age = input("age:")job = input("Job:")hobbies = input("hobbies:")info = '''---------------info of------------Name    :%s             #代表nameAge     :%s             #代表AgeJob     :%s             #代表JobHobbies :%s             #代表Hobbies'''%(name,age,job,hobbies)print(info)msg = "my name is %s"%('shawn')print(msg)  # my name is shawnmsg = "my name is %s,my age is %s"%('shawn',22)print(msg)  # my name is shawn,my age is 22

2.2.2、使用xxx.format形式

msg = "my name is {}".format('shawn')print(msg)  # my name is shawnmsg = "my name is {} my age is {}".format('shawn',18)print(msg)  # my name is shawn my age is 18msg = "my name is {0} my age is {1} {1}".format('shawn',18)print(msg)  # my name is shawn my age is 18 18

2.2.3、f”{}”的形式

name = 'shawn'age = 18print(f"my name is {name} my age is {age}") # my name is shawn my age is 18

3、Python的数据结构

3.1、数字

  在Python中例如(2、4、6)归类为int整形数据,带有小数的数字(3.14、9.9)称之为float(浮点型)。在Python3中使用 ”/“ 返回的永远是一个浮点型,想要除法最后获得整形类型就使用”//“,即可实现,但是最后的整形数据是直接截掉后面的小数,不是四舍五入之后的数据;想要获取两数之后的余数使用”%“即可实现。实现指数运算直接使用”**“来实现。此外python还支持复数来定义虚实部计算。

print(7 / 3)print(7 // 3)print(7 % 3)print(7 ** 3)输出结果:2.333333333333333521343

3.2、字符串

  在Python中可以使用”‘’“(单引号)或““””(双引号)括起来代表字符串,也可以使用”\“来对特殊的字符转意,如果想要表示多行字符串,就可以使用” “”“…”“” “ 三个双引号或者” ‘’‘…’‘’ “ 三个单引号把字符串扩起来,每行结尾都会被自动加上一个换行符,如果不想输出换行符可以在每行的最后加入 ”\“ 来避免输出行换。

print("""How are you?\I`m fine.""")print("""How are you?I`m fine.""")

3.3、数据结构之通用序列操作

  Python中有六种内置序列,其中三种基本序列类型比较常见:列表、元组、字符串,大部分序列都可以进行通用操作,包括索引、切片、相同序列相加、乘法、成员资格、长度、最大值、最小值。

3.3.1、索引

  序列的每一个元素都会为其分配一个数字,代表序列中的索引的位置,Python中的索引和C语言的素组一样的从下标0开始的。不仅可以正数,还可以倒数,负数时候索引的值从右往左的顺序。

x1 = [1, 2, 3, 4]print("列表", x1[0])x2 = (1, 2, 3, 4)print("元组", x2[0])print("元组", x2[-1])输出结果:列表值 1元组值 1元组值 4

3.3.2、切片

  利用切片我们可以连续的获取或者输出一段序列的内容,在Python中所有的序列类型都可以支持切片操作。切片主要有三个参数组成 切片索引开始值 :切片索引结束值 :切片步距 固定格式就是这样的但是我们并不一定看到的都是这样的格式,可以进行一些简写格式的输出。

x1 = [1, 2, 3, 4, 5, 6, 7, 8]print(x1[:])        # 全切片print(x1[0:7:2])    # 指定开始结束位置以及步距的切片print(x1[3:])       # 指定开始默认一直到结束 步距默认为1print(x1[:3])       # 指定结束位置默认索引从0下标开始算起 步距默认为1print(x1[3:5])      # 指定开始结束位置 步距默认为1 位置为左开右闭 即第五位不输出,索引从0下标开始算起 索引数字3的时候开始切片 数字5的时候结束,但不包括索引下标5所对应的值print(x1[-6:5])     # 切片值支持负数索引输出结果:[1, 2, 3, 4, 5, 6, 7, 8][1, 3, 5, 7][4, 5, 6, 7, 8][1, 2, 3][4, 5][3, 4, 5]

3.3.3、序列相加

  序列相加直接可以使用 ”+“进行连接操作。

x1 = [1, 2, 3, 4, 5, 6, 7, 8]x2 = [9, 10, 11, 12, 13, 14]x3 = x1 + x2print(x3)输出结果:[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]

3.3.3、序列重复

  序列重复直接使用 “*” 星号叫做乘号

x1 = [1, 2, 3, 4, 5, 6, 7, 8]x3 = x1 * 2print(x3)输出结果:[1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8]

3.3.4、成员资格

  成员资格使用 “in” 来判断一个元素是否包含在序列中,如果包含则返回True,不包含则返回False ,但是在进行包含判断的只有元素的类型和值完全一致的时候次才可以实现包含,例如不能使用字符5和数字5来处理包含关系。

x1 = [1, 2, 3, 4, 5, 6, 7, 8]print("5是否包含在x1  列表中", 5 in x1)print("10是否包含在x1 列表中", 10 in x1)输出结果:5是否包含在x1  列表中 True10是否包含在x1 列表中 False

3.3.4、长度、最值、求和

x1 = [1, 2, 3, 4, 5, 6, 7, 8]print("x1列表长度值", len(x1))print("x1列表最大值", max(x1))print("x1列表最小值", min(x1))print("x1列表总和值", sum(x1))输出结果:x1列表长度值 8x1列表最大值 8x1列表最小值 1x1列表总和值 36

3.4、列表

  列表在Python中的功能是非常强大的,也是Python中重要的数据结构。列表也可以通过索引来获取和设置数,我们还可以增删和查找列表中的数据。列表不单纯是一类数据还可以是多种类型的数据。

3.4.1、新增元素

  Python中的新增数据可以使用append来添加,但是只在列表中新增一个元素,想要新增多个元素的话还的使用extend,append无论后面是一个列表都只能按照一个元素新增上去,extend则是打散之后加载在原来的列表后面。使用append和extend都是只能在列表的最后面添加元素,我们可以使用insert在指定的序列添加指定的数据。insert和append一样的一此只能插入一个数据,insert插入的序列下标还是从0开始计算的。

x1 = [1, 2, 3, 4, 5, 6, 7, 8]print("x1修改前的数据", x1)x1[3] = "Hello"print("x1修改后的数据", x1)x1.append("Python")print("x1 append 新增后的数据", x1)   # append只能新增一个元素x1.append([168, 99])print("x1 append 新增后的数据", x1)   # append只能新增一个元素x1.extend([99, 168])print("x1 extend 新增后的数据", x1)   # append只能新增一个元素x1.insert(1, 66)print("x1 insert 新增后的数据", x1)   # append只能新增一个元素输出结果:x1修改前的数据 [1, 2, 3, 4, 5, 6, 7, 8]x1修改后的数据 [1, 2, 3, 'Hello', 5, 6, 7, 8]x1 append 新增后的数据 [1, 2, 3, 'Hello', 5, 6, 7, 8, 'Python']x1 append 新增后的数据 [1, 2, 3, 'Hello', 5, 6, 7, 8, 'Python', [168, 99]]x1 extend 新增后的数据 [1, 2, 3, 'Hello', 5, 6, 7, 8, 'Python', [168, 99], 99, 168]x1 insert 新增后的数据 [1, 66, 2, 3, 'Hello', 5, 6, 7, 8, 'Python', [168, 99], 99, 168]

3.4.2、删除元素

  对于列表能新增当然也可以删除元素,删除元素可以使用pop和remove来实现,pop是默认删除最后一个元素,并且返回删除的元素值,此外pop也可以通过参数指定删除哪一个数值,remove的话是可以根据值索引删除的。remove删除值之后不会返回删除的值,直接返回一个None,remove删除时候索引找不到该值的时候就会报错。另外也可以使用del来实现列表元素的删除。

x1 = [1, "Hello", 3, 4, 5, 6, 7, 8]print("使用pop删除之后的返回值", x1.pop())print("删除之后的值", x1)print("使用pop删除之后的返回值", x1.pop(0))print("删除之后的值", x1)print("使用remove删除之后的返回值", x1.remove("Hello"))print("删除之后的值", x1)del x1[4]print("del删除之后的值", x1)输出结果:使用pop删除之后的返回值 8删除之后的值 [1, 'Hello', 3, 4, 5, 6, 7]使用pop删除之后的返回值 1删除之后的值 ['Hello', 3, 4, 5, 6, 7]使用remove删除之后的返回值 None删除之后的值 [3, 4, 5, 6, 7]del删除之后的值 [3, 4, 5, 6]

3.4.3、查找元素

  Python中使用index方法查找元素在列表中的索引位置。当查找元素不存在的时候会报错。除此之外列表还有些其他的方法可以操作,例如反转队列reverse、计算元素在列表中出现的次数count、排序sort。注意排序sort的时候需要列表的数据类型一致,不然会报错。

x1 = [1, 2, 3, 4, 5, 6, 7, 8]print("index 查找元素位置", x1.index(2))x1.reverse()print("reverse 反转列表数据", x1)print("count 计数列表数据值出现次数", x1.count(6))x1.sort()print("sort 排序后的列表", x1)输出结果:index 查找元素位置 1reverse 反转列表数据 [8, 7, 6, 5, 4, 3, 2, 1]count 计数列表数据值出现次数 1sort 排序后的列表 [1, 2, 3, 4, 5, 6, 7, 8]

3.5、元组

  元组与列表十分相似,但是元组与列表最大的区别是列表可以修改、读取、删除,而元组创建之后则不能修改和不能删除单个元素。元组还是有count、和index的可以索引和对元素计数的。
 元组的定义只需要使用() 括号扩起来就行了,并且使用 ”,“ 来隔开元素。 如果只有一个元素的时候单单只有一个 ”()“是不够的,还需要加上一个 ”,“才能定义一个元组。

x1 = (1, 2, 3, 6, 9)print("count 计数元组数据值出现次数", x1.count(6))print("count 计数元组数据元素的索引值", x1.index(6))x2 = ("Hello")x3 = ("Hello",)print("x2数据类型", type(x2), "x3数据类型", type(x3))输出结果:count 计数元组数据值出现次数 1count 计数元组数据元素的索引值 3x2数据类型 <class 'str'> x3数据类型 <class 'tuple'>

3.6、字典

  字典的类型就和json字符串类似的样子,你可以像查字典一样的使用字典。字典类中的元素由键key和值value构成元素和元素之间使用 ”,“分隔整个字典使用 ”{}“ 花括号来包围。字典中的键必须是唯一的不能重复,如果是空字典的话直接使用 {} 表示。
 字典可以通过key值去获取和索引字典元素中的值,此外还可以对字典的数据进行增删操作等等。

  • del:来实现对字典的删除
  • clear:对字典进行清空处理
  • copy:可以实现对字典的复制
  • fromkeys:可以实现创建一个字典
  • get:放回键对应的值
  • keys:返回一个列表,里面包含所有的键,通常判断一个键是否包含于字典中我们只需要得到这个列表在使用 ”in“ 操作即可实现
  • value:返回一个列表,包含字典的所有值
  • items:方法放回一个列表,包含所有的键和值列表,由于字典不能直接用for循环,通常我们可以使用iteams方法来遍历整个字典
language = {    "chinese": "汉语",    "english": "英语",    "other": "其他语言"}print("原来值", language)print("索引值", language["chinese"])language["other"] = "鸟语"print("设定值", language["other"])language["alien"] = "火星语"print("新增值", language["alien"])del language["other"]print("删除后值", language)language.clear()print("clear 后的值", language)language_key = ("chinese", "english", "other")language = dict.fromkeys(language_key)language1 = dict.fromkeys(language_key, "汉语")language2 = language1.copy()print("字典language", language, "\n字典language1", language1, "\n字典language2", language2)print("获取字典language1的值", language1.get("chinese"))print("chinese 是否存在字典中?", "chinese" in language1.keys())print("language1 中所有的值", language1.values())print("使用items遍历字典")for k, v in language1.items():    print("key:", k, "value:", v)输出结果:原来值 {'chinese': '汉语', 'english': '英语', 'other': '其他语言'}索引值 汉语设定值 鸟语新增值 火星语删除后值 {'chinese': '汉语', 'english': '英语', 'alien': '火星语'}clear 后的值 {}字典language {'chinese': None, 'english': None, 'other': None} 字典language1 {'chinese': '汉语', 'english': '汉语', 'other': '汉语'} 字典language2 {'chinese': '汉语', 'english': '汉语', 'other': '汉语'}获取字典language1的值 汉语chinese 是否存在字典中? Truelanguage1 中所有的值 dict_values(['汉语', '汉语', '汉语'])使用items遍历字典key: chinese value: 汉语key: english value: 汉语key: other value: 汉语

3.7、集合

  集合的数与列表数据非常类似,集合唯一的区别是不会包含重复的数据。如果是空集合一定要使用set()来定义,如果包含元素则可以使用 “{}” 来定义,在集合中可以使用add来添加对应的元素,也可以使用remove来移除集合中的数,但是不能用来移除不存在的数,不然Python会报错。

empty = set()  # 注意空集合不能使用{}定义print("空集合", empty)number = {1, 5, 1, 10}print("数字集合", number)mix = {1, "hello", 3.1415926}print("混合集合", type(mix), mix)mix.add("66")print("添加后的元素", mix)mix.remove("hello")print("移除后的元素", mix)输出结果:空集合 set()数字集合 {1, 10, 5}混合集合 <class 'set'> {1, 3.1415926, 'hello'}添加后的元素 {1, '66', 3.1415926, 'hello'}移除后的元素 {1, '66', 3.1415926}

3.8、推导公式

  Python中支持三种推导公式,风别对应列表、字典、集合;推导公式的用法用中括号扩起来中间用for语句,后面跟着用if语句作为判断,满足条件的传到for语句前面用作构建列表。

a1 = [x for x in range(5)]print("列表a1", a1)odd = [x for x in range(5) if x % 2 != 0]print("列表odd", odd)d1 = {n: n**2 for n in range(5)}print("字典d1", d1)d2 = {v: k for k, v in d1.items()}print("字典d2", d2)s1 = {i ** 2 for i in [-1, -2, 1, 3, -3]}print("集合d1", s1)输出结果:列表a1 [0, 1, 2, 3, 4]列表odd [1, 3]字典d1 {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}字典d2 {0: 0, 1: 1, 4: 2, 9: 3, 16: 4}集合d1 {1, 4, 9}

4、Python 的流程控制

4.1 if判断

4.1.1、单独if 语句

输出格式:

if 表达式:语句1语句2......

  python是严格的遵守索引的,语句或者一段表达式都是按照缩进来实现区分的,因此语句1、语句2前面的空格是严格遵守一个tab键,四个空格的。i放后面的表达式不仅仅支持布尔类型的,还可以是数字,只有数字0才会判断为 False,如果测试的为字符串只有空字符串的条件结果才会为False,空列表、空元组、空字典的条件才会是False。

list1 = []set1 = set()str1 = ""number1 = 0if list1:    print("输出False")if set1:    print("输出False")if str1:    print("输出False")if number1:    print("输出False")    

4.1.2、else语句

  当前面的if语句执行不满足条件的时候才会执行后面的else语句。

list1 = []if list1:    print("list1 不是一个空列表")else:    print("list1 是一个空列表")输出结果:list1 是一个空列表

4.1.3、elif语句

  当if的判断有超过两种情况的时候这个时候elif就可以排上用场了。

number = 18if number < 16:    print("小于16")elif number == 16:    print("等于16")else:    print("大于16")    输出结果:大于16

4.2 循环

4.2.1、while循环

输出格式:

while 表达式:语句1语句2......

  while循环语句是python中最简单的循环语句。while和if都是一样的条件测试,只有满足条件才会执行。

number = 6while number <= 9:    print("number:", number)    number += 1    输出结果:number: 6number: 7number: 8number: 9

4.2.2、for循环

输出格式:

for 变量 in 序列:语句1语句2......

  for循环比while循环的用法更加丰富,其中序列可以是列表元组等其他可迭代的对象,变量逐一遍历 in后面序列中的每一个元素。

for x in range(1, 10):    print("变量值", x)    输出结果:变量值 1变量值 2变量值 3变量值 4变量值 5变量值 6变量值 7变量值 8变量值 9

4.2.3、break、continue、和pass值

   对于循环for 和 while而言,没有任何中断打断则会直至循环条件不满足和遍历整合序列之后才会结束,我们可以使用break和continue实现对特定情形下实现退出循环;break的作用是立即退出循环体,直接结束循环,continue并不会立即退出循环,而是跳过当前的循环体执行之后的循环。在Python中 pass语句就是空语句,其中的作用就是保持程序语句的完整性。

for x in range(1, 5):    if x == 2:        break    print("变量值", x)print("continue 输出")for x in range(1, 5):    if x == 2:        continue    print("变量值", x)x = 35if x == 35:    pass    # 注释 TODO 没有 pass就会报错else:    print("输出value")        输出结果:变量值 1continue 输出变量值 1变量值 3变量值 4

4.2.4、循环中的else

  在Python中 while和for都可以结合else一起使用,当while测试为False的时候就会执行else,但是值得注意的是中途中遇到break就不会在执行else,同样for也是一样的情况。

number = 1while number < 5:    number += 1    print("变量值", number)else:    print("执行else之后的变量值", number)print("continue 输出")for x in range(1, 5):    if x == 2:        break    print("变量值", x)else:    print("执行else之后的变量值", x)输出结果:变量值 2变量值 3变量值 4变量值 5执行else之后的变量值 5continue 输出变量值 1

4.3、运算符

4.3.1、算术运算符

操作符描述示例
+加法:运算两侧值相加a+b
减法:运算两侧值相加a-b
*乘法:左操作数乘上有操作数a*b
/除法:左操作数除于有操作数a/b
%取模:返回左操作数除于右操作数的余数a%b
**幂:返回幂之后的数a**b
//取整除:放回商的部分a//b

4.3.2、比较运算符

操作符描述示例
==等于:比较对象是否相等a == b
!=不等于:比较对象是不否相等a != b
>大于:返回a 是否大于ba > b
<小于:返回a 是否小于ba< b
>=大于等于:返回a 是否大于等于ba >= b
<=小于等于:返回a 是否小于等于ba <= b

4.3.3、赋值运算符

操作符描述示例展开形式
=右边值赋给左边a = 100a = 100
+=右边值加到左边a += 100a = a + 100
-=右边值减到左边a -= 100a = a – 100
*=左边值除于右边值a *= 100a = a * 100
/=左边值对右边进行取模运算a /= 100a = a / 100
**=左边值对右边进行幂运算a **= 100a = a ** 100
//=左边值整除右边a //= 100a = a // 100

4.3.4、位运算符

操作符描述示例
&按位与: 对应a、b都为1则为1 否则为0a & b
|按位或: 对应a、b都为0则为0 否则为1a | b
^按位异或: 对应a、b都相同则0 否则为1a ^ b
~按位取反:运算符操作数里的每一位 0变1,1变0~ a
<<按位左移运算符:左移操作数按位左移右操作数指定的位数a << b
>>按位右移运算符:左移操作数按位右移右操作数指定的位数a >> b

4.3.4、逻辑运算符

操作符描述示例
and逻辑与:两个都为真 则为真,否则为假a and b
or逻辑或:两个中一个为真即为真a or b
not逻辑非:反转操作数的逻辑,真变为假,假变为真not a

5、函数

  Python中与C、C++、以及java中的函数还是有些区别的。一般C和C++函数的开始都是返回类型,但是Python并不是这样的。定义一个函数只要以 def 开头即可。其中函数名和c、c++一样的,有效函数名以字母或下划线开头,后面可以跟字母、数字、下划线。
输出格式:

def function_name(arg1, arg2):    function body    return value......

5.1、函数参数

  在创建函数的时候,可以设置参数也可以不用设置参数,在python函数参数有以下几种:

必须参数: 调用函数时必须要传入参数,并且传入参数的顺序也要一致。
关键字参数: 使用关键字可以不按函数定义时的参数顺序传入参数。
默认参数: 在函数定义时我们可以给函数添加默认值,在调用时没有传入参数就是用默认参数。
可变参数: 在定义函数时候不能确定使用时候的参数个数和内容时候就可以使用可变参数。

# 必须参数def add(a, b):    print("a + b = ", a + b)# 关键字型参数def person(name, age):    print("姓名:", name, "\n年龄:", age)# 默认参数def default_args(name="wsp", age="24"):    print("名字:", name, "年龄:", age)# 可变参数1def function_args_one(*args):    print(args)# 可变参数2def function_args_two(**kwargs):    print(kwargs)# 组合可变参数3def function_args_three(*args, **kwargs):    print(args, kwargs)add(99, 168)person(age=18, name="小花")default_args()function_args_one("小花", 18, "上海")function_args_two(name="小花", age=18, location="上海")function_args_three(1, 2, 3, 4, 5, 6, name="小花", age=18)输出结果:a + b =  267姓名: 小花 年龄: 18名字: wsp 年龄: 24('小花', 18, '上海'){'name': '小花', 'age': 18, 'location': '上海'}(1, 2, 3, 4, 5, 6) {'name': '小花', 'age': 18}

  关键字型参数参数可以不按照函数定义时候的顺序赋值,可变参数 * args参数获取到的是一个元组,这也是它能作为卡变参数的原因。 ** kwargs 参数获取到是一个字典,再调用函数的时候就要使用关键字的方式来传递参数。

5.2、函数返回值

  如果我们想要返回函中的局部变量,使用return作为函数的返回值。不需要像c或者c++在函数前面声明返回数据类型。函数中没有return或者return后面没有跟上任何参数,则返回数据为None。python的返回值还有更高的特性就是返回值不止一个可以有多个返回值。

# 单独一个值的返回类型def add(a, b):    print("a + b = ", a + b)    return a + b# 多个值的返回类型def function_args():    str1 = "args1"    str2 = "args2"    str3 = "args3"    return str1, str2, str3print(add(66, 99))r1, r2, r3 = function_args()print("函数返回值", r1, r2, r3)输出结果:a + b =  165165函数返回值 args1 args2 args3

5.3、变量的作用域

  在C语言中我们都经常会使用到的变量,分为局部变量和全局变量,在python中也是一样的,分为局部变量和全局变量。
  在函数内赋值的变量,一般不做特殊声明的变量都是局部变量;在函数外赋值的变量就是全局变量。如果想要对函数外的变量进行修改的话,这个时候我们可以使用global关键字。

# 全局变量str1 = "外部参数1"str2 = "外部参数2"def function_one():    global str2    str1 = "内部参数1"    print(str1)    print(str2)    str2 = "函数内修改外部参数2"    print(str2)function_one()print(str1)输出结果:内部参数1外部参数2函数内修改外部参数2外部参数1

5.4、Lambda表达式

  表达式也称为匿名函数,类似于c语言中的三目运算符。具体形式以lambda开头,之后内容以”:”分为两部分,左边是函数的参数,右边是要返回的值。lambda表达式中就不需要return作为返回值了。

f = lambda x, y: x + yz = f(66, 99)print(z)输出结果:165

6、面向对象

6.1、介绍

  python和C++都是面向对象的,最基本的就是类、继承和封装。接下来简单的介绍一下面向对象的基本特征。

专有名词解释
用来描述具有相同的属性和方法的对象的集合。
类变量类变量在整个实例化的对象中是公用的,类变量定义在类中并在函数体之外,通常不作为实例变量使用。
数据成员类变量或者实例变量,用于处理类机器实例化对象的相关数据。
方法重写如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫做方法的覆盖,也称为方法的重写。
实例变量定义在方法中的变量,只作用于当前实例的类。
继承指一个派生的类继承基类的字段和方法。继承也允许把一个派生类的对象作为一个基类对待。
实例化创建一个类的实例,一个类的具体对象。
方法类中定义的函数。
对象通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法。

  python对象介绍示例。

# Animal 类class Animal:    # 类属性 类属性是类体中所有方法外的变量 称之为类属性或类变量    Animal = 'animal'    # 构造函数 创建类的时候就会调用    def __init__(self):        # 实例属性 类体中方法内并且命名格式为 self.变量名 = 变量值,称之为实例变量        self.get_name = "animal"    # 实例变量        print("实例化类时候调用")    def name(self):        print("Animal")    # 类方法 必须要有self    def set_name(self, name):        self.get_name = name        # 在类方法中直接可以引用实例变量        # 局部变量 类体中方法内 以 变量名 = 变量值的方式, 称之为局部变量        set_name = self.get_name    # set_name 局部变量        print("设置后的数据", set_name, self.get_name)    # 析构函数    def __del__(self):        print("销毁类的时候调用")# Cat 继承Animalclass Cat(Animal):    # 多态性 在新类中可以重新定义一个方法和父类中一样的方法    def name(self):        print("Cat Animal")    # 静态方法 没有self    @staticmethod    def Cat_name():        print("my name is cat")Cat.Cat_name()          # 直接调用静态方法不用实例化化成类都可以调用cat = Cat()             # 实例化 catcat.name()              # 调用方法 这个时候由于多态性调用的是Cat下面的方法cat.set_name("小猫咪")   # 调用父类中的方法,实现继承方法输出结果:my name is cat实例化类时候调用Cat Animal设置后的数据 小猫咪 小猫咪销毁类的时候调用

6.2、定义语法

输出格式:

class [类名]:    [语法块]......eg:class new_class:    pass

6.3、类的使用

  在使用类之前需要实例化类,类实例化之后成为常见的对象,创建对象和创建变量类似,需要先声明对象是哪个类,同时指明变量名称。

class new_class:    passNEW_CLASS = new_class()print(NEW_CLASS)输出结果:<__main__.new_class object at 0x000001BEE34BB190>

6.4、类的构造方法

  很多类都可能需要有特定的初始化状态,所以一个类可以定义一个特殊的方法,叫做够构造函数,在python中构造函数就是类的__init__方法,这个和c++的构造的命名有些不同。注意构造函数中的第一个参数是 self,不能遗漏,构造方法返回值必须None

class new_class:    def __init__(self):        print("我是类中的构造方法")    passNEW_CLASS = new_class()输出结果:我是类中的构造方法 

6.5、类的属性

  在构造方法中我们可以初始化一些属性,注意属性(或者叫做成员变量、类变量)必须加上“self” 加上点的方式赋值,不能直接定义变量,直接定义变量的周期会函数内,函数执行完之后就会销毁。其实函数中的 __init__的第一个参数self指的是实例本身,在C++中对应的就是this指针。

class new_class:    def __init__(self, name):        self.name = name        self.age = 18        location = "上海"  # 直接定义变量 函数执行后就会销毁NEW_CLASS = new_class("小花")print(NEW_CLASS.name)print(NEW_CLASS.age)        # 调用类中属性print(NEW_CLASS.location)   # 执行会报错输出结果:Traceback (most recent call last):  File "xxx\xx.py", line 11, in <module>    print(NEW_CLASS.location)   # 执行会报错AttributeError: 'new_class' object has no attribute 'location'小花18

6.5、类的方法

  在类中定义的函数我们称为方法,类中的方法和函数定义基本相同,除了方法必须定义在类里并且第一个参数必须是self(参数名不强制要求为self但是一般使用名字self以其它参数作为区分)外,其它和函数定义没有区别。

class new_class:    def __init__(self, name):        self.name = name        self.age = 18    def class_function(self):        print("我是类中的方法")NEW_CLASS = new_class("小花")NEW_CLASS.class_function()输出结果:我是类中的方法

6.5、私有属性

  在c++中我们对成员变量的声明有 public、private、protected 在引用类的时候对类中成员变量的访问会受到限制,python中私有属性中只要在定义属性的时候使用两条下划线作为开头,解释器就会认为这个属性是私有属性,外部不能谁便访问这个属性。我们虽然可以给私有属性赋值,但是不能直接使用类中的私有属性。

class new_class:    def __init__(self, name):        self.__name = name        self.age = 18NEW_CLASS = new_class("小花")# 调用其中的私有属性就会报错print(NEW_CLASS.__name)

6.5、私有方法

  私有方法和私有属性一样的只可以在类不被操作和调用。

class new_class:    def __method(self, name):        self.__name = name        self.age = 18NEW_CLASS = new_class("小花")# 调用其中的私有属性就会报错NEW_CLASS.__method()

6.6、继承和多态

  面向对象编程具有三大特性,封装性、继承性、多态性。

6.6.1、继承

  继承指的是保留原有的基础上在拓展新的属性和方法。原来的类称为父类,后来的类称为子类。在Python中支持多重继承,像java是不支持多重继承的,python是支持多重继承的。
输出格式:

class new_class(Baseclass1, Baseclass2):    语法块......

  要定义从那个父类继承,只需要在定义子类的名字后面的括号类填入父类的名字,多重继承的时候只需要使用“,”来隔开就行。继承的时候需要注意两点:

  • 在继承中如果子类定义了构造函数,则父类的构造方法__init__不会被自动调用,需要在子类的构造方法中专门调用。
  • 子类不能继承父类中的私有方法,也不能调用父类的私有方法。
class class_one:    def __init__(self, name):        self.name = name    def class_one_function_name(self):        print("class_one_function_name input name", self.name)class class_two:    def __init__(self, name):        self.name = name    def class_two_function_name(self):        print("class_two_function_name input name", self.name)    def __private_class(self):        print('class_two 私有类', self.name)# 单继承class new_class_one(class_one):    pass# 多继承class new_class_two(class_one, class_two):    pass# 继承中子类定义构造函数class new_class_three(class_one):    def __init__(self):        super(new_class_three, self).__init__("小花")# 实例化对象print("NEW_class_one")NEW_class_one = new_class_one("小花")NEW_class_one.class_one_function_name()print("NEW_class_two")NEW_class_two = new_class_two("小花")# 新类多重继承NEW_class_two.class_one_function_name()NEW_class_two.class_two_function_name()# 不能直接调用父类中的私有方法 不然会报错# NEW_class_two.__private_class()print("NEW_class_three")NEW_class_three = new_class_three()NEW_class_three.class_one_function_name()输出结果:NEW_class_oneclass_one_function_name input name 小花NEW_class_twoclass_one_function_name input name 小花class_two_function_name input name 小花NEW_class_threeclass_one_function_name input name 小花

6.6.2、多态

  继承可以帮助我们重复使用代码,但是有时候子类的行为不一定和父类一样。下面就是我们在调用同一个方法时候我们想要输出不同的结果的时候却是输出相同的结果。

class Animal:    def name(self):        print("Animal")class Cat(Animal):    passclass Dog(Animal):    passAnimal_Cat = Cat()Animal_Cat.name()Animal_Dog = Dog()Animal_Dog.name()输出结果:AnimalAnimal

  在python中的多态和c++中的虚函数一样表达意思,python中子类的方法会覆盖掉的父类中相同名字的方法。

class Animal:    def name(self):        print("Animal")class Cat(Animal):    def name(self):        print("Cat")    passclass Dog(Animal):    def name(self):        print("Dog")    passAnimal_Cat = Cat()Animal_Cat.name()Animal_Dog = Dog()Animal_Dog.name()输出结果:CatDog

6.6.3、类知识拓展

类变量和实例变量
 类变量不需要实例化直接使用,相当于绑定在类上,不是实例化上。但是类变量在实例化之后也是可以被调用的。注意实例不能修改类变量。类变量的形式:

  • 可变变量作为类变量:对于列表、字典、自定义类这些可变变量,如果将其作为类变量,则是传引用。即所有对象的类变量公用一个内存地址。
  • 不可变变量作为类变量:对于INT,STRING这种不可变变量,如果将其作为类变量,则是传值。即所有对象的类变量有各自的内存地址。

  不管是可变变量还是不可变变量,只要是放在构造函数中,则都是传值。即各个对象拥有自己的对象属性。

class Animal:    name = "Animal"     # 类变量    age_number = [1, 2, 3]print(Animal.name, Animal.age_number)               # 没有实例化对象就可以直接调用类变量animal = Animal()print("实例化后输出类变量", animal.name)               # 实例化后的对象继承了类变量animal.name = "animal"animal.age_number[0] = 6print("在实例化对象中修改类变量后类变量的结果", Animal.name, Animal.age_number)   # 可以看出修改实例化后的对象中的类变量不会影响类中类变量print("实例化后对象中的属性结果", animal.name, animal.age_number)               # 可以看出修改实例化后的对象中的类变量不会影响类中类变量Animal.name = "animal 修改"Animal.age_number[0] = 9print("直接修改类变量后看看实例化后的对象中的属性结果", animal.name, Animal.age_number)   # 直接修改类变量之后实例化中的属性没有被修改print("直接修改类变量后类成员是结果", Animal.name, Animal.age_number)           # 直接修改类变量之后实例化中的属性没有被修改输出结果:Animal [1, 2, 3]实例化后输出类变量 Animal在实例化对象中修改类变量后类变量的结果 Animal [6, 2, 3]实例化后对象中的属性结果 animal [6, 2, 3]直接修改类变量后看看实例化后的对象中的属性结果 animal [9, 2, 3]直接修改类变量后类成员是结果 animal 修改 [9, 2, 3]

静态方法与类方法
 静态方法的语法是在定义函数的上面一行(不能含有空格行)添加一句@staticmethod,这一句是装饰器,在后面信号与槽中也会涉及到装饰器的用法,静态方法中不需要有第一个参数 “self“,因此静态方法不能调用其类的成员方法和成员变量,静态方法不需要实例化之后才可以使用,而是和类变量一样的方法就可以直接使用,和其他一般函数使用没有任何区别。

class Animal:    @staticmethod    def name():        print("Animal")Animal.name()输出结果:Animal

7、模块

7.1、模块介绍

  模块就是一个包含了python定义和声明的”.py“文件。例如我们定义”main.py“的文件。

7.2、__name__变量

  模块的模块名可以通过全局变量 ”name“ 获得。

class Animal:    @staticmethod    def name():        print("Animal")if __name__ == "__main__":    Animal.name()    输出结果:Animal

7.2、dir函数

  dir函数可以列出对象的模块标识符,标识符有函数、类和变量。

import PySide2print(dir(PySide2))输出结果:['__all__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', '__version__', '__version_info__', '_additional_dll_directories', '_setupQtDirectories', 'dedent', 'os', 'print_function', 'support', 'sys']

7.3、使用模块

  使用模块一种直接的方法直接 ”import“ 要导入的文件,还有另外一种导入模块的方法就是”from … import …“,它可以然我们导入模块中指定的一部分到我们当前命名空间。

import PySide2from PySide2 import QtGuiprint(dir(PySide2))

7.4、错误和异常

7.4.1、错误

  在python中最容易的错误就是python中的语法错误。

print aaa
7.4.2、异常

  在前面的学习python中时不时偶尔弄错就会出现报错,报错在pycharm 中会提示我们错误在那个位置中,我们很快定位错误的位置,但是在运行一些逻辑或者代码的时候会发生意向不到的错误,运行时候检测到的错误称为异常,有些错错误并是致命的,但是大多数的异常都不做处理,python解析器会输出异常信息到屏幕上别且终止运行。

print(1 / 0)str1 = "1"print(str1 + 0)dir1 = {}print(dir1["name"])输出结果:都是运行错误
7.4.3、异常处理

  异常处理是在运行中才会检测到的,但是有时候是不可预测的。有些错误不是最致命的,但是我们像需要程序继续执行到结束,python中提供了异常处理。
输出格式:

try:    语法块except 异常处理:处理异常语法块

  在异常处理中我们需要关键字 try和except ,java也是一样的关键字,使用这两个关键字来捕获异常处理出现异常处理我们就会跑到异常处理处去处理对应的异常。ValueError是内建的一个异常,其中python中内建的异常处理还有许多的。其中我们 可以使用except as 来获取不同类型的错误。

while True:    try:        number = int(input("输入一个数字:"))    except ValueError:        print("输入的不是数字")输出结果:输入一个数字:12输入一个数字:as输入的不是数字输入一个数字:try:    print(1 / 0)except ValueError:   # 只接受特定错误    print("发生遗产处理")try:    # print("1" + 1)    print(1 / 0)except Exception as e:   # 接受所有类型错误    print("发生遗产处理", e)
7.4.4、finally子句

  在python中finally子句需要和try子句一起使用,finally子句在异常处理中的作用是:无论是否有异常处或者是否捕获了异常,finally最终都会执行,这个处理在以后可能会使用到的数据库和文件处理中是非常有用的。

try:    print(1 / 0)except ValueError:    print("发生遗产处理")finally:    print("到处一游")输出结果:Traceback (most recent call last):  File "xxx\xxx.py", line 2, in <module>    print(1 / 0)ZeroDivisionError: division by zero到处一游

8、多线程和进程

8.1.进程基本概念

  进程是计算机运行程序的实体,曾经是分时系统的基本运作单位,进程本身不是基本运行单位,而是线程的容器。

8.2.线程基本概念

  线程是操作系统进行运算调度的最小单位,它包含在进程中,是进程中的实际运行单位。一个进程是一个单一顺序的控制流,一个线程可以运行多个进程,每个线程并行执行不同的任务。

8.3.python线程模块

_thread模块
 标准库中的_thread模块作为最低级别的模块存在,一般不建议使用,但是在一些简单的场合还是可以使用的,因为标准库的用法十分简单,标准库的核心其实就是start_new_thread方法,_thread.start_new_thread(function, args, kwargs=None)。
 启动一个新线程并返回其标识符,线程使用参数列表args(必须是元组)执行函数,可选的kwargs是指定关键字的参数的字典,当线程返回时,线程将以静默方式退出。

import timeimport datetimeimport _threaddata_time_format = "%H:%M:%S"def get_time_str():    now = datetime.datetime.now()    return datetime.datetime.strftime(now, data_time_format)def thread_function(thread_id):    print("thread %d\t start at %s" % (thread_id, get_time_str()))    print("thread %d\t sleeping", thread_id)def main():    print("Main thread start at %s" % get_time_str())    for i in range(5):        _thread.start_new_thread(thread_function, (i, ))        time.sleep(1)    time.sleep(6)    print("Main thread finish at %s" % get_time_str())if __name__ == "__main__":    main()    输出结果:Main thread start at 20:24:24thread 0 start at 20:24:24thread %d sleeping 0thread 1 start at 20:24:25thread %d sleeping 1thread 2 start at 20:24:26thread %d sleeping 2thread 3 start at 20:24:27thread %d sleeping 3thread 4 start at 20:24:28thread %d sleeping 4Main thread finish at 20:24:35

Threading.thread
 在python中标准库中提供_thread这样底层线程模块,还提供了threading模块,线程不仅提供了面向对象的线程方式,还提供了各种有用的对象和方法方便我们创建和控制线程。

import timeimport datetimeimport threadingdata_time_format = "%H:%M:%S"def get_time_str():    now = datetime.datetime.now()    return datetime.datetime.strftime(now, data_time_format)def thread_function(thread_id):    print("thread %d\t start at %s" % (thread_id, get_time_str()))    print("thread %d\t sleeping", thread_id)def main():    print("Main thread start at %s" % get_time_str())    threads = []    # 创建线程    for i in range(5):        thread = threading.Thread(target=thread_function,args=(i,))        threads.append(thread)    # 启动线程    for i in range(5):        threads[i].start()        time.sleep(1)    # 等待线程执行完毕    for i in range(5):        threads[i].join()    print("Main thread finish at %s" % get_time_str())if __name__ == "__main__":    main()    输出结果:Main thread start at 20:50:39thread 0 start at 20:50:39thread %d sleeping 0thread 1 start at 20:50:40thread %d sleeping 1thread 2 start at 20:50:41thread %d sleeping 2thread 3 start at 20:50:42thread %d sleeping 3thread 4 start at 20:50:43thread %d sleeping 4Main thread finish at 20:50:44

  我们在调用threading.Thread对象的方法start,才会正真的启动线程,最后调用threading.Thread对象中的join方法来等待线程的结束,使用threading.Thread模块可以自动的帮助我们实现管理线程锁。

二、QT知识

2.1、初识QT

  Qt的跨平台特性非常的强,一套代码不用修改太多,直接通用所有的平台。
可运用于MCU上,QT的三驾马车,QT下的串口串口编程、QT下的网络编程、QT下操作GPIO。

2.2、Python下怎么结合QT一起开发设计

  对于前面我们使用的是QT Designer软件 ,软件中就已经集成好了我们C++ 的开发和调试环境了,并且还有一堆的demo,我们现在使用python来开发怎么实现 界面设计之后与python代码结合起来呢,这里我使用的编程软件是pycharm,它是一款python的专门编程的IDE,也是非常好用的,我们可以使用社区版本的,可以免费使用。pycharm也可安装许多的包,就我们在前面开始的时候介绍的pyside2、PyQT5都只是python下的一个包,这个包主要的作用就是将QT设计的界面中元素翻译成Python的类,相当于一个翻译官一样的;我这里使用的是Pyside2,在安装好Pyside2包之后在包中就会有一个pyside2-designer.exe的可执行文件,这个可执行文件包含QT设计师的界面设计,就没有QT界面设计师的全部C++的编译调试的全部功能,主要的功能就是界面设计,设计好之后我们就可以保存.ui的文件,然后后面就可以使用pyside2-uic.exe可执行文件将我们的界面翻译成Python类,我们只要实例化类之后就可以正常的像其他的python类一样使用。

2.2.1、安装Pyside2 包

  对于前面我们使用的QT Designer 设计我们的界面,其中该软件是直接配合C++一起使用的,在python中的包pyside2中也包含了pyside2-designer.exe、pyside2-rcc.exe、pyside2-uic.exe几个软件,我们只需要在pycharm中安装pyside2就可以了。
 对于怎样安装pyside2,我这里有两种方法。
方法一:


方法二:
 这里以fibs来举例,因为我已经安装好了Pyside2。我们写入import Pyside2 ,然后会出现Pyside2 底部红线,旁边有个小红灯泡,我们直接点击红灯泡然后会提示出下面提示,我们直接选择install package Pyside2 就会自动的安装。

2.2.2、在pycharm中添加插件

  我们在安装好Pyside2之后,我们怎样将 pyside2-designer.exe、pyside2-rcc.exe、pyside2-uic.exe这几个exe文件加入我们的pycharm中呢?这几个文件又在何处找到呢?在将这几个可执行文件与pycharm关联起来我们使用的是pycharm中的外部工具,后面我们就可以很快捷的利用设定好的外部工具直接将我们的QT设计好的界面以及界面所需要的资源转换成对应的python代码。一般这几个可执行文件在我们安装好pyside2包之后就可以找得到的,你可以直接在全局文件夹中搜索。我的是安装好包之后自己在我的工程文件夹下面新建一个venv的文件,然后在该文件夹中的Script中就可以看到这三个可执行文件了。

  那么我们怎样配置这几个可执行文件呢,下面都是一些操作步骤,首先需要在File中打开设置选项。然后找到Tools下面的External Tools。

2.2.2.1 配置pyside2-designer外部工具

  按照下面的截图我们在步骤三中填写下我们外部工具的名字name,这个是随便我们怎么填写的方便自己记忆就行了。我这里填写的是Qt Designer;接着在Program中我们可以点击右边的文件夹的图标就会打开文件夹我们只需要根据我们安装Pyside2中的pyside2-designer.exe的路径选择即可,我这里它自动安装在F:\QT\qt-uart-Python\venv\Scripts\pyside2-designer.exe路径下的,每个人的路径不一定相同,但是最后都要选中对应的可执行文件;最后的Working directory 工作目录就是需要填写$ProjectFileDir$这样就会每次运行的时候会在当前工作目录下。我们可以这个外部工具快速的打开designer软件来实现我们界面的编辑。

Name:Qt DesignerProgram:F:\QT\qt-uart-Python\venv\Scripts\pyside2-designer.exeWorking directory:$ProjectFileDir$


2.2.2.2 配置pyside2-uic外部工具

  和前面配置pyside2-designer一样的步骤,我们点击加号新建新的外部工具。然后填写对应的配置即可。但是我们这个和前面designer有些区别的就是我们需要设置下Arguments 参数,将文件转换成对应的参数,这里需要这样配置,每个人的配置要相同的:-o $FileNameWithoutExtension$.py $FileName$这个转换器的主要作用就是将我们的QT设计的ui文件转换成对应的Python类,在后面我们可以以类的方式来引用界面中的方法以及属性。

Name:Pyside2-uicProgram:F:\QT\qt-uart-Python\venv\Scripts\pyside2-uic.exeArguments :-o $FileNameWithoutExtension$.py $FileName$Working directory:$ProjectFileDir$

2.2.2.3 配置Pyside2-rcc外部工具

  和前面配置pyside2-designer一样的步骤,也需要点击加号新建新的外部工具。然后填写对应的配置即可。这里也需要我们需要设置下Arguments 参数,将文件转换成对应的参数,这里需要这样配置,每个人的配置要相同的:$FileName$ -o $FileNameWithoutExtension$_rc.py 这里这个转换器的主要作用可以将我们设计器中的图片资源转换成python类,后面可以在python中引用。

Name:Pyside2-uicProgram:F:\QT\qt-uart-Python\venv\Scripts\pyside2-uic.exeArguments :$FileName$ -o $FileNameWithoutExtension$_rc.pyWorking directory:$ProjectFileDir$


 最后这里是配置好的外部工具的图片截图。

2.3、认识pyside2-designer界面

  这里界面和我们之前使用的一样的,基本上也没啥好介绍的。一般新建文件我都是以MainWindow样式来新建的。

2.4、使用QT界面设计师设计串口助手界面

  在前面的C++那里已将介绍了QT界面设计师的基本使用,我们这里就直接设计下界面,界面的预览就是这样的,我们在下面来详细的介绍下这个界面的所有设计。

2.4.1、数据接收区设计

  数据接收区我这里使用的是Display Widgets下面的Text Browser的控件。其中需要修改控件属性中objectName: tB_Serial_Data_RX_Show,修改控件名称便于我们后面应用类的时候知道是那个控件,此外我们接收数框是不可以编辑的,还需要设置属性readyOnly是选中状态以及最小minimumSize中的高度为160,minimumSize的意思就是在界面的拉大和缩小中最小保持的尺寸,没有设计默认为0,就会随着窗口的拉大和缩小一起有比例的变化。


2.4.2、串口信息配置区

  串口信息配置区这里我们使用的是Combo Box 、label 控件,Combo Box控件主要是可以提供下拉列表选项,label控件主要是我们下拉列表选项的主要内容提示,例如串口号就是提示旁边的这个下拉列表的作用就是选择串口号。

波特率中 label 控件的使用介绍
 这里使用了好几个Combo Box 和label进行组合,我主要介绍一个,后面的都一样的,这里主要介绍波特率,label就很简单了,只需要为们修改label属性中的text即可,objectName可修改和不修改,这里后面不会调用到类中的方法对该属性修改,我这里是顺便修改了。objectName:label_Serial_Baud_Rate,text:波特率,此外我这里还修改了minimumSize和sizePolicy中的水平伸展,minimumSize前面已经介绍过作用了,sizePolicy的作用我们这里设置水平伸展为1,旁边的波特率下拉选项控件我们设置为2,在波特率这里我们将label和Combox Box两个组件放在一个horizontalLayout水平布局中,我们label中水平伸展设置为1,Combox Box设置为2,整体horizontalLayout被分为3分,label占用的面积就是horizontalLayout的总宽度的1/3;Combox Box就占用总宽度的2/3。

波特率中 Combox Box控件的使用介绍
  在波特率中Combox Box的设置相对于label就复杂些,首先还是需要设置属性中的objectName、sizePolicy、minimumSize这几个参数。这几个参数在下面截图中,另外我们还看到下拉选项框中默认选中的9600波特率这个数字,我们该怎么设置呢?首先我们直接双击当前波特率这个Combox Box控件,就会跳出编辑组合框,从下面截图中可以看到编辑组合框中有+ -图标,我们点击+的图标,就会有新建项目,我们直接双击新建项目字体就可以修改其数值,设置好后选择OK即设置好了,- 号可以删除我们不想要的数据,我们选择确认之后组合编辑框默认显示的是我们设置列表中的第一个数字,以下图设置的话默认就是1200,但是我们不想要它默认显示1200,我想要设置9600,我们在属性栏中currentindex里设置3,或者选中这个设置之后旁边有上下加减的选项我们根据我们的需求调到9600即可。



 串口信息配置区中的其他几个的设置都是一样的,在数据位中我们设置数据为5、6、7、8;停止位中设置数据为1、1.5、2;校验位中设置数据为无、奇校验、偶校验

2.4.3、串口控制区

  在串口控制区主要有一个标志的ID识别以及我们控制输出的输入框以及控制发送的按键以及控制串口打开关闭的按键以及和数据接收、发送清空按键。
主要是三大类控件,分别是label、Push Button、以及用来输入的Line Edit。

ID显示label
 WSP-自制Python串口助手这几个字其实就是一个label来显示的,我们这里主要设置属性中的text,可以根据个人喜好设置不同的字体。还有属性中的alignment对齐我们在水平这里设置为居中即可。

串口数据发送框
 这里我们使用Line Edit作为串口数据的发送信息输入框,这里没有什么特别的参数需要修改的,就是需要我们将名字修改即可。

串口控制按钮
 串口控制按钮都是使用Push Button控件来实现的,这里主要修改属性中的text、objectName即可,其他的没有修改什么,除了发送数据按钮maximumSize被设置在70以外,其他的都只是设置text和objectName。
 在布局方面我们将打开串口、关闭串口、清空接收数据、清空发送数据都放在horizontalLayout控件中,然后中间放置几个horizontalSpacer控件(看起来像弹簧),几个弹簧的作用就是将控件的间隙弹开。

2.4.4、MainWindow的参数设置

  我们界面中所有控件都包含在MainWindow中的QMainWindow这个类下的。我们需要设置下这个类的以及属性值,我们这里设置geometry下面的宽度和高度属性,这个属性就是我们开始运行软件的时候界面最初始状态的尺寸,即打开软件的尺寸大小,我们还需要设置软件名称以及配置一个icon图标。


 在设置图标中我们这里这里使用的是以.ico结尾的图片作为图标的图片的,还可以为后面软件打包之后形成可执行文件的图标,即是我们打包之后生成.exe的文件显示的图标。在加入这个图片我们需要在该工程下新建一个文件夹,专门用来存放我们的图片资源的images文件夹,以及一个qt中专门用来管理图片类资源的一个.qrc文件,这里我新建res.qrc文件。在这个文件中我们可以使用pycharm打开,然后编辑编写一些代码,这里是有格式要求的。我这里配置如下:

<RCC>    <qresource prefix="image">        <file>images/uart_icon.ico</file>    </qresource></RCC>

  这里是有固定的格式的,具体我们可以参考前面C++中添加图片资源后生成的qrc文件中自动生成的格式。我们就只需要添加images/uart_icon.ico这个就是我们图片资源存放的位置,需要添加其他的图片资源也是类似的。


 接下来我们将图片资源加入其中的具体步骤:

 最后是我们加载成功之后的显示效果。

 我们将图片资源库引用进来之后我们就可以设置下我们MainWindow中icon图片了,我们选择选择资源库,然后选择我们想要的图片即可。


 上面是我们将串口助手中的所有界面都布局和默认参数都设置好了,所有的组件的命名和布局在下图可见。

 在上面的介绍中我们就将串口布局设置好了,然后我们就直接保存,保存的文件是以ui文件名结尾的文件。其实这个文件就类似与前端的html文件一样的格式,其实QT中的许多东西和前端中的html、CSS都差不多的,有许多的相似处,我们还可以在QT 界面设计师中对控件通过改变样式实现不同风格的样式显示,其中的样式语法就和前端中的CSS一样的,渲染控件达到美化界面的效果。好了到此我们界面简单的设计以及布局都已经实现了,接着我们只需要将ui文件转换成对应的python文件,然后在python文件中直接作为类引用。

三、Python实现串口助手通讯逻辑

  在前面已经实现了我们QT 界面设计师设计好了我们的串口助手的界面,接着我么继续将对应设计的界面变为python类,来实现我们串口助手的通讯逻辑交互。

3.1、将QT 界面设计师的文件转换成python文件

  在我们在对待前面设计的.ui文件资源在python中引用有两种方法,一种是直接通过文件的方式来引用,另外一种是直接将ui文件转换成python文件,我们使用的是将ui文件转换成python文件,这样我们在后面调用类的时候非常的方便,在使用直接调用ui文件动态加载的话后面对于我们使用到界面中的方法在pycharm就不会有代码提示。值得注意的是当我们的ui布局变化的时候都要重新转换生成python文件。
 在前面中我们以已经将外部工具配置好了,也设计好ui界面了,这里我们使用工具将ui文件转换成py文件。转换成功之后我们文件夹中就会多出来一个ui_Serial.py的python文件,打开文件还有一个波浪线红色错误提示,这是因为我们对应的图片资源还没有转成对应的python文件的原因。我也使用外部工具pyside2-rcc即可将我们qrc文件转换成对应的python文件。


 如果转换之后你发现python代码中字体有些小的话,可以在设置中设置字体的大小。

  接着我们在文件夹中新建两个python文件,分别为main.py和main_Screen.py这两个文件。这两个文件的主要作用就是main_Screen.py实现整个ui多个界面代码类的封装,main.py是整个上位机python代码的入口,整个执行的开始就从这个python文件开始执行的。
 在main.py中的代码如下:

import sysfrom PySide2.QtWidgets import QApplicationfrom main_Screen import QmyMain_Screen# 初始化窗口系统并且使用在argv中的argc个命令行参数构造一个应用程序对象appapp = QApplication(sys.argv)# 实例化对象my_Main_Screen = QmyMain_Screen()# 调用类中的方法my_Main_Screen.show()# 1.app.exec_()的作用是运行主循环,必须调用此函数才能开始事件处理,调用该方法进入程序的主循环直到调用exit()结束。# 主事件循环从窗口系统接收事件,并将其分派给应用程序小部件。如果没有该方法,那么在运行的时候还没有进入程序的主循环就# 直接结束了,所以运行的时候窗口会闪退。# app.exec_()在退出时会返回状态代码# 2.不用sys.exit(app.exec_()),只使用app.exec_(),程序也可以正常运行,但是关闭窗口后进程却不会退出。# sys.exit(n)的作用是退出应用程序并返回n到父进程sys.exit(app.exec_())

  在main_Screen.py中的代码如下:

from PySide2.QtWidgets import QMainWindowfrom ui_Serial import Ui_MainWindow  # 调用包中的Ui_MainWindow类class QmyMain_Screen(QMainWindow):    # 析构函数 类结束之后调用的方法    def __del__(self):        print("__del__")    # 构造函数,在调用类之后就会执行的方法    def __init__(self, parent=None):        super(QmyMain_Screen, self).__init__(parent)    # 调用父类中的__init__方法        self.ui = Ui_MainWindow()                       # 实例化类 我们在这里定义实例化对象名带有ui 这样我们后面在调用的时候就可        # 以方便知道那部分是ui中的方法,那部分是我们逻辑中大方法,实现ui和逻辑代码的区分和隔离        self.ui.setupUi(self)                           # 调用ui中类 实现界面加载显示

  在上面的代码中我们只是实现测试将ui文件转换成python代码之后是否正常运行的测试,还没有加入整个逻辑交互,我们在main.py中右键运行此python代码看看是否能正常运行。如果和我下面运行的结果一样的话就证明整个python和pyside2的环境搭建正确。

3.2、python 串口逻辑实现

  运行到上面这张图片后接下来我们继续来实现我们串口交互的代码编写,这里我们使用的包是pyserial,包的安装和前面一样的。其中关于pyserial的全部信息我们可以在它的pyserial官网上可以看到,包括怎样具体的使用都有详细的介绍。pyserial这个模块封装了串口的访问。它为运行在Windows、OSX、Linux、BSD(可能是任何POSIX兼容系统)和IronPython上的Python提供后端。名为“serial”的模块自动选择适当的后端。值得注意的是我们安装包名字是pyserial,但是我们导入包却是使用import serial来导入包。
 我们想要实现逻辑交互就要知道我们的逻辑业务,即是想要实现什么功能。

  • 实现串口数据收发操作。
  • 当串口插入或者拔出的时候检测实时状态。

  第一个功能我们可以根据pyserial模块来实现,第二个功能的话要么我们捕获当前系统串口事件发生通知我们程序,然后我们重新加载当前的串口列表,要是我们就用一个定时器隔一段时间检测我们系统的串口是否发生变化,发生变化的时候我们就刷新我们的串口列表,没有刷新的时候就保持不变。
 这里我采用的是第二种方法来实现串口变更检测的。在检测我们串口变化我们使用的也是serial包下的tool来实现的,具体详细的介绍我们可以参考官网。我们直接使用下面这段代码就可以当前电脑上的串口的获取,返回的是列表数据。当获取到串口列表之后我们可以隔一段时间来获取电脑的串口,保存上一个状态与当前扫描的状态比较两个列表是否有变化,当串口没有打开的时候我们就将最新的变化的添加到我们的界面中串口号组合编辑框中;当我们串口当前状态在打开的时候我们检测到变化,看下变化是不是少了我们当前连接的这个串口,这样我们就可以确定我们当前连接的串口是否被拔出,如果被拔出,可以直接弹出弹框提示,如不不是当前连接的串口可以不做处理都行的。

Serial_Port_list = list(serial.tools.list_ports.comports())

  上面中业务面怎样实现我都将逻辑整理清楚了,接着就是我们需要些什么知识。

  1. QT中的定时器,扫描串口变化
  2. pyserial中tool工具检测串口
  3. pyserial中对于串口操作包括打开、关闭、数据发送、数据接收
  4. 数据接收与页面显示联动 信号与槽来实现

3.2.1、QT中定时器

  在pyside2中引用QT定时器的包是下面的代码。我们可以直接点击进入类可以查看在它的内部有哪些类和方法,这里我列举一些我用到的。

from PySide2.QtCore import QTimerself.Serial_Port_Check_Timer = QTimer()                                             # 实例化类self.Serial_Port_Check_Timer.setInterval(1000)                                      # 设置定时器时间周期     self.Serial_Port_Check_Timer.timeout.connect(self.Serial_Port_Check_Timer_Timeout)  # 设置并关联定时器槽函数self.Serial_Port_Check_Timer.start()                                                # 启动定时器

3.2.2、信号与槽

  具体可以参考这篇文章
 如果你第一次打开出现这样的错误的化,就按照提示安装下 PowerShell,之后打开 PowerShell,我这里系统是windows11的,第一次也是出错了许多的。

 然后我们输入 set-ExecutionPolicy RemoteSigned,如果你的会报错就和我的一样,报错为下所示。

PS C:\Windows\System32> set-executionpolicy remotesignedSet-ExecutionPolicy: Access to the path 'C:\Program Files\WindowsApps\Microsoft.PowerShell_7.3.1.0_x64__8wekyb3d8bbwe\powershell.config.json' is denied.To change the execution policy for the default (LocalMachine) scope, start PowerShell with the "Run as administrator" option. To change the execution policy for the current user, run "Set-ExecutionPolicy -Scope CurrentUser".

  接着我们输入指令Get-ExecutionPolicy -List,看下policy,如果你和我的一样的话我们需要将CurrentUser设置为 RemoteSigned才可以我们可以输入Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser,然后再用 Get-ExecutionPolicy -List看下最后是不是设置成功了。
  设置成功之后的状态,我的设置号这样之后就OK,然后在pycharm中再次打开终端即我的就好了。

 接着我们使用pyinstaller打包,打包指令为pyinstaller -F -w main.py,-F 直接打包成一个exe可执行文件,我们还可以使用‘-D’,-D 打包成一个目录;-c 带控制台的方便查看打包之后打开出现错误的查看,-w 不带控制台的,这个确保你的打包软件没有问题就使用这个,建议第一次打包使用 -c来实现,如果出现错误,我们就使用命令行提示符 具体方法 win + r 键 输入 cmd,之后就可以打开了,然后就是将路径设置到我们当前打包的路劲之后dist文件夹下,在命令行提示符中运行main,exe,就可以查看到错误了,我这里没有问题就没啥错误,先指定到某一个盘,我这里指定到F盘 输入F:之后使用cd 命令到文件夹路径,最后执行exe软件;main.py 是脚本名字,根据你自己当前要打包的文件名而定。

  输入打包指令之后就开始打包,看到下面打包成功就说明打包成功了,然后我们可以看到我们工程下有一个dist文件夹,在文件夹中就可以看到我们打包好的exe文件了,我们可以重命名就可发给别人使用了。虽然能用,但是还是有点小瑕疵就是exe文件的图标是python图标,我们想要换成我们自己的图标还需要一点点处理。

  修改打包的exe软件图标,具体的做法为下面指令 -i 为图标 icon,后面跟上我们图标位置。再次打包,还是出现和上面一样的打包成功,然后我们打开dist文件夹看看,发小图标没有变,这个是window缓存问题,我们将dist文件复制到其他地方发现就会变了,好了,我们python串口上位机到此就结束了,花了几天的时间每天都是下班之后回来慢慢的写的,也花费了许多时间的,确实学习需要有记录,不然时间久了就相当于白学了。

 pyinstaller -F -w -i F:\QT\qt-uart-python\dist\images\uart_icon.ico main.py


3.2.5、源码地址

  源码地址