手把手教你如何使用python写串口调试助手

电子说

1.3w人已加入

描述

绪论

  笔者这里使用的是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

  对于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))   # '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             #代表name
Age     :%s             #代表Age
Job     :%s             #代表Job
Hobbies :%s             #代表Hobbies
'''%(name,age,job,hobbies)
print(info)
msg = "my name is %s"%('shawn')
print(msg)  # my name is shawn
msg = "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 shawn
msg = "my name is {} my age is {}".format('shawn',18)
print(msg)  # my name is shawn my age is 18
msg = "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 = 18
print(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.3333333333333335
2
1
343

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 = [1234]
print("列表", x1[0])

x2 = (1234)
print("元组", x2[0])
print("元组", x2[-1])

输出结果:
列表值 1
元组值 1
元组值 4

3.3.2、切片

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

x1 = [12345678]

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

输出结果:
[12345678]
[1357]
[45678]
[123]
[45]
[345]

3.3.3、序列相加

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

x1 = [12345678]
x2 = [91011121314]

x3 = x1 + x2
print(x3)

输出结果:
[1234567891011121314]

3.3.3、序列重复

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

x1 = [12345678]

x3 = x1 * 2
print(x3)

输出结果:
[1234567812345678]

3.3.4、成员资格

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

x1 = [12345678]

print("5是否包含在x1  列表中"5 in x1)
print("10是否包含在x1 列表中"10 in x1)

输出结果:
5是否包含在x1  列表中 True
10是否包含在x1 列表中 False

3.3.4、长度、最值、求和

x1 = [12345678]

print("x1列表长度值", len(x1))
print("x1列表最大值", max(x1))
print("x1列表最小值", min(x1))
print("x1列表总和值", sum(x1))

输出结果:
x1列表长度值 8
x1列表最大值 8
x1列表最小值 1
x1列表总和值 36

3.4、列表

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

3.4.1、新增元素

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

x1 = [12345678]

print("x1修改前的数据", x1)
x1[3] = "Hello"
print("x1修改后的数据", x1)
x1.append("Python")
print("x1 append 新增后的数据", x1)   # append只能新增一个元素
x1.append([16899])
print("x1 append 新增后的数据", x1)   # append只能新增一个元素
x1.extend([99168])
print("x1 extend 新增后的数据", x1)   # append只能新增一个元素
x1.insert(166)
print("x1 insert 新增后的数据", x1)   # append只能新增一个元素

输出结果:
x1修改前的数据 [12345678]
x1修改后的数据 [123'Hello'5678]
x1 append 新增后的数据 [123'Hello'5678'Python']
x1 append 新增后的数据 [123'Hello'5678'Python', [16899]]
x1 extend 新增后的数据 [123'Hello'5678'Python', [16899], 99168]
x1 insert 新增后的数据 [16623'Hello'5678'Python', [16899], 99168]

3.4.2、删除元素

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

x1 = [1"Hello"345678]

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'34567]
使用pop删除之后的返回值 1
删除之后的值 ['Hello'34567]
使用remove删除之后的返回值 None
删除之后的值 [34567]
del删除之后的值 [3456]

3.4.3、查找元素

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

x1 = [12345678]

print("index 查找元素位置", x1.index(2))

x1.reverse()
print("reverse 反转列表数据", x1)

print("count 计数列表数据值出现次数", x1.count(6))

x1.sort()
print("sort 排序后的列表", x1)

输出结果:
index 查找元素位置 1
reverse 反转列表数据 [87654321]
count 计数列表数据值出现次数 1
sort 排序后的列表 [12345678]

3.5、元组

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

x1 = (12369)

print("count 计数元组数据值出现次数", x1.count(6))

print("count 计数元组数据元素的索引值", x1.index(6))

x2 = ("Hello")
x3 = ("Hello",)
print("x2数据类型", type(x2), "x3数据类型", type(x3))

输出结果:
count 计数元组数据值出现次数 1
count 计数元组数据元素的索引值 3
x2数据类型 <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, "
字典language1", language1, "
字典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 是否存在字典中? True
language1 中所有的值 dict_values(['汉语''汉语''汉语'])
使用items遍历字典
key: chinese value: 汉语
key: english value: 汉语
key: other value: 汉语

3.7、集合

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

empty = set()  # 注意空集合不能使用{}定义
print("空集合", empty)
number = {15110}
print("数字集合", number)
mix = {1"hello"3.1415926}
print("混合集合", type(mix), mix)
mix.add("66")
print("添加后的元素", mix)
mix.remove("hello")
print("移除后的元素", mix)

输出结果:
空集合 set()
数字集合 {1105}
混合集合 <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(5if 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-213-3]}
print("集合d1", s1)

输出结果:
列表a1 [01234]
列表odd [13]
字典d1 {00112439416}
字典d2 {00114293164}
集合d1 {149}

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 = 0


if 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 = 18

if 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 = 6

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

4.2.2、for循环

输出格式:

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

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

for x in range(110):
    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(15):
    if x == 2:
        break
    print("变量值", x)

print("continue 输出")
for x in range(15):
    if x == 2:
        continue
    print("变量值", x)


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

4.2.4、循环中的else

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

number = 1
while number < 5:
    number += 1
    print("变量值", number)
else:
    print("执行else之后的变量值", number)

print("continue 输出")
for x in range(15):
    if x == 2:
        break
    print("变量值", x)
else:
    print("执行else之后的变量值", x)

输出结果:
变量值 2
变量值 3
变量值 4
变量值 5
执行else之后的变量值 5
continue 输出
变量值 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 是否大于b a > b
< 小于:返回a 是否小于b a< b
>= 大于等于:返回a 是否大于等于b a >= b
<= 小于等于:返回a 是否小于等于b a <= b

4.3.3、赋值运算符

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

4.3.4、位运算符

操作符 描述 示例
& 按位与:对应a、b都为1则为1 否则为0 a & b
| 按位或:对应a、b都为0则为0 否则为1 a | b
^ 按位异或:对应a、b都相同则0 否则为1 a ^ 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, "
年龄:", age)


# 默认参数
def default_args(name="wsp", age="24"):
    print("名字:", name, "年龄:", age)


# 可变参数1
def function_args_one(*args):
    print(args)


# 可变参数2
def function_args_two(**kwargs):
    print(kwargs)


# 组合可变参数3
def function_args_three(*args, **kwargs):
    print(args, kwargs)


add(99168)
person(age=18, name="小花")
default_args()
function_args_one("小花"18"上海")
function_args_two(name="小花", age=18, location="上海")
function_args_three(123456, name="小花", age=18)

输出结果:
a + b =  267
姓名: 小花 
年龄: 18
名字:wsp 年龄: 24
('小花'18'上海')
{'name''小花''age'18'location''上海'}
(123456) {'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, str3


print(add(6699))
r1, r2, r3 = function_args()
print("函数返回值", r1, r2, r3)


输出结果:
a + b =  165
165
函数返回值 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 + y

z = f(6699)
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 继承Animal
class Cat(Animal):
    # 多态性 在新类中可以重新定义一个方法和父类中一样的方法
    def name(self):
        print("Cat Animal")

    # 静态方法 没有self
    @staticmethod
    def Cat_name():
        print("my name is cat")


Cat.Cat_name()          # 直接调用静态方法不用实例化化成类都可以调用
cat = Cat()             # 实例化 cat
cat.name()              # 调用方法 这个时候由于多态性调用的是Cat下面的方法
cat.set_name("小猫咪")   # 调用父类中的方法,实现继承方法

输出结果:
my name is cat
实例化类时候调用
Cat Animal
设置后的数据 小猫咪 小猫咪
销毁类的时候调用

6.2、定义语法

输出格式:

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

6.3、类的使用

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

class new_class:
    pass


NEW_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("我是类中的构造方法")
    pass


NEW_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 "xxxxx.py", line 11in 
    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 = 18


NEW_CLASS = new_class("小花")

# 调用其中的私有属性就会报错
print(NEW_CLASS.__name)

6.5、私有方法

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

class new_class:
    def __method(self, name):
        self.__name = name
        self.age = 18


NEW_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_one
class_one_function_name input name 小花
NEW_class_two
class_one_function_name input name 小花
class_two_function_name input name 小花
NEW_class_three
class_one_function_name input name 小花

6.6.2、多态

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

class Animal:
    def name(self):
        print("Animal")


class Cat(Animal):
    pass


class Dog(Animal):
    pass


Animal_Cat = Cat()
Animal_Cat.name()

Animal_Dog = Dog()
Animal_Dog.name()

输出结果:
Animal
Animal

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

class Animal:
    def name(self):
        print("Animal")


class Cat(Animal):
    def name(self):
        print("Cat")
    pass


class Dog(Animal):
    def name(self):
        print("Dog")
    pass


Animal_Cat = Cat()
Animal_Cat.name()

Animal_Dog = Dog()
Animal_Dog.name()

输出结果:
Cat
Dog

6.6.3、类知识拓展

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

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

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

class Animal:
    name = "Animal"     # 类变量
    age_number = [123]


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


输出结果:
Animal [123]
实例化后输出类变量 Animal
在实例化对象中修改类变量后类变量的结果 Animal [623]
实例化后对象中的属性结果 animal [623]
直接修改类变量后看看实例化后的对象中的属性结果 animal [923]
直接修改类变量后类成员是结果 animal 修改 [923]

静态方法与类方法  静态方法的语法是在定义函数的上面一行(不能含有空格行)添加一句@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 PySide2

print(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 PySide2
from PySide2 import QtGui

print(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 "xxxxxx.py", line 2in 
    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 time
import datetime
import _thread

data_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	 start at %s" % (thread_id, get_time_str()))
    print("thread %d	 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:24
thread 0  start at 20:24:24
thread %d  sleeping 0
thread 1  start at 20:24:25
thread %d  sleeping 1
thread 2  start at 20:24:26
thread %d  sleeping 2
thread 3  start at 20:24:27
thread %d  sleeping 3
thread 4  start at 20:24:28
thread %d  sleeping 4
Main thread finish at 20:24:35

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

import time
import datetime
import threading

data_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	 start at %s" % (thread_id, get_time_str()))
    print("thread %d	 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:39
thread 0  start at 20:50:39
thread %d  sleeping 0
thread 1  start at 20:50:40
thread %d  sleeping 1
thread 2  start at 20:50:41
thread %d  sleeping 2
thread 3  start at 20:50:42
thread %d  sleeping 3
thread 4  start at 20:50:43
thread %d  sleeping 4
Main 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:QTqt-uart-PythonvenvScriptspyside2-designer.exe路径下的,每个人的路径不一定相同,但是最后都要选中对应的可执行文件;最后的Working directory  工作目录就是需要填写$ProjectFileDir$这样就会每次运行的时候会在当前工作目录下。我们可以这个外部工具快速的打开designer软件来实现我们界面的编辑。

Name:    Qt Designer
Program:   F:QTqt-uart-PythonvenvScriptspyside2-designer.exe
Working directory: $ProjectFileDir$

串口

 

串口

2.2.2.2  配置pyside2-uic外部工具

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

Name:    Pyside2-uic
Program:   F:QTqt-uart-PythonvenvScriptspyside2-uic.exe
Arguments :   -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-uic
Program:   F:QTqt-uart-PythonvenvScriptspyside2-uic.exe
Arguments :   $FileName$ -o $FileNameWithoutExtension$_rc.py
Working 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打开,然后编辑编写一些代码,这里是有格式要求的。我这里配置如下:


    "image">
        images/uart_icon.ico
    

  这里是有固定的格式的,具体我们可以参考前面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 sys
from PySide2.QtWidgets import QApplication
from main_Screen import QmyMain_Screen

# 初始化窗口系统并且使用在argv中的argc个命令行参数构造一个应用程序对象app
app = 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 QMainWindow
from 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 QTimer

self.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、信号与槽

  具体可以参考这篇文章Qt --- 信号与槽在这片文章中说到信号的本质为:信号的本质就是事件。槽(Slot)就是对信号响应的函数。槽本质就是一个函数,当我们将信号绑定一个槽函数的时候,信号即使事件发生就会执行槽函数。在QT中信号的发出者是某个实例化的类对象,对象内部可以进行相关事件的检测。  我们可以使用connect来将一个函数绑定到某个信号,disconnected来解除绑定,emit来实现事件的触发。信号的定义  QmyMain_Screen_Uart_RX_Data_Signal 是直接从Qobject中继承而来的,我们定义了一个信号,这个信号定义为类的属性text是一个两种类型的参数,接着我们实例化了Uart_RX_Data_Signal对象。

# 定义数据接收类
class QmyMain_Screen_Uart_RX_Data_Signal(QObject):
    text = Signal(QTextBrowser, str)    # 定义信号
# 实例化类
Uart_RX_Data_Signal = QmyMain_Screen_Uart_RX_Data_Signal()

信号的发射  通过信号的emit函数发送信号,如果信号关联了槽函数,则会执行槽函数,没有关联则不会响应。这里直接将我们串口接收到的数据直接通过信号发射出去,然后,信号有槽函数绑定,这样就会执行我们的槽函数。

Uart_RX_Data_Signal.text.emit(self.ui.tB_Serial_Data_RX_Show, get_data.decode('utf-8'))

信号与槽的绑定  我们使用connect将信号与槽函数绑定。在信号发射的时候就会将self.ui.tB_Serial_Data_RX_Show, get_data.decode('utf-8')两个参数传输到我们槽函数,这样就完成了信号与槽的交互过程。

Uart_RX_Data_Signal.text.connect(self.Uart_RX_Data_Signal_Print_Data_to_Gui)            # 连接槽函数

# 静态方法 作为被关联的槽函数
@staticmethod
def Uart_RX_Data_Signal_Print_Data_to_Gui(fb, text):
 fb.append(str(text))                        # 添加数据
 fb.ensureCursorVisible()                    # 确保光标可见
 fb.moveCursor(fb.textCursor().End)          # 将光标移动到最后

  对于按键类的信号与槽都是一样的,上面这个是我们自己定义的信号,按键点击事件clicked已经是一个信号,因此我们只需要为它绑定一个槽函数即可。所有代码中最重要的就是上面这几个知识点,其他的都是python的一些基本东西。  我直接将所有的代码贴在下面。

3.2.3、通讯交互源码

  下面就是通讯所有的源码,难点我都有详细的备注。main_Screen.py中代码

import sys

from PySide2.QtCore import Slot, QTimer, QObject, Signal
from PySide2.QtWidgets import QMainWindow, QApplication, QMessageBox, QTextBrowser
from ui_Serial import Ui_MainWindow  # 调用包中的Ui_MainWindow类
import serial
from serial.tools import list_ports
import threading


# 定义数据接收类
class QmyMain_Screen_Uart_RX_Data_Signal(QObject):
    text = Signal(QTextBrowser, str)    # 定义信号


# 实例化类
Uart_RX_Data_Signal = QmyMain_Screen_Uart_RX_Data_Signal()


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中类 实现界面加载显示

        self.Serial = serial.Serial()           # 实例化串口
        self.Serial_Next_Port_Number = []       # 上次串口号
        self.Serial_Open_state = False          # 串口打开标志
        # 创建一个线程用来接收串口数据
        self.Serial_Data_RX_Thread = threading.Thread(target=self.Serial_Data_RX_Thread_Function, daemon=True)

        self.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()                                                    # 启动定时器

        self.Serial_Next_Port_Number = self.Serial_Scan_Uart_Port()                             # 获取下当前的所有
        self.ui.cBox_Serial_Port_Number.addItems(self.Serial_Next_Port_Number)                  # 将串口数据添加到组合编辑框中

        self.ui.pBtn_Serial_Send_Data.clicked.connect(self.pBtn_Serial_Send_Data_Slot)          # 连接槽函数
        self.ui.pBtn_Serial_Open.clicked.connect(self.pBtn_Serial_Open_Slot)                    # 连接槽函数
        self.ui.pBtn_Serial_Close.clicked.connect(self.pBtn_Serial_Close_Slot)                  # 连接槽函数
        self.ui.pBtn_Serial_Clear_RX_Data.clicked.connect(self.pBtn_Serial_Clear_RX_Data_Slot)  # 连接槽函数
        self.ui.pBtn_Serial_Clear_TX_Data.clicked.connect(self.pBtn_Serial_Clear_TX_Data_Slot)  # 连接槽函数
        Uart_RX_Data_Signal.text.connect(self.Uart_RX_Data_Signal_Print_Data_to_Gui)            # 连接槽函数

    # 静态方法 作为被关联的槽函数
    @staticmethod
    def Uart_RX_Data_Signal_Print_Data_to_Gui(my_QTextBrowser, text):
        my_QTextBrowser.append(str(text))                                    # 添加数据
        my_QTextBrowser.ensureCursorVisible()                                # 确保光标可见
        my_QTextBrowser.moveCursor(my_QTextBrowser.textCursor().End)         # 将光标移动到最后

    # 串口接收数据线程
    def Serial_Data_RX_Thread_Function(self):
        while True:
            try:
                if self.Serial.is_open:                     # 串口打开则接收数据
                    get_data = self.Serial.readline()       # 读取串口数据
                    if get_data.decode('utf-8'):            # 将str数据转换成 utf-8格式判断其是否不为空
                        # 信号发送数据,信号绑定槽函数就会执行槽函数
                        Uart_RX_Data_Signal.text.emit(self.ui.tB_Serial_Data_RX_Show, get_data.decode('utf-8'))
            except Exception as error:
                print(error)

    # 定时器检测串口号是否发生变化
    def Serial_Port_Check_Timer_Timeout(self):
        now_port = self.Serial_Scan_Uart_Port()         # 获取当前的所有串口
        if now_port == self.Serial_Next_Port_Number:    # 串口不变的情况下
            pass
        else:  # 串口有变化的时候
            # 当前连接的串口不在了 可能被拔出了
            try:
                if self.Serial.is_open and (now_port != self.Serial_Next_Port_Number):
                    QMessageBox.information(self, "Error""串口已拔出", QMessageBox.Yes, QMessageBox.Yes)
                    # print("当前连接的串口不在了", now_port)
                    self.Serial.close()                             # 关闭串口
                    self.ui.pBtn_Serial_Close.setEnabled(True)      # 使能点击状态
                    self.ui.pBtn_Serial_Open.setEnabled(True)       # 使能点击状态
            except Exception as error:
                print(error)
            finally:
                # print("串口发生变化", now_port)
                self.ui.cBox_Serial_Port_Number.clear()             # 先清空原有数据
                self.ui.cBox_Serial_Port_Number.addItems(now_port)  # 将变化的添加到列表中
                self.Serial_Next_Port_Number = now_port             # 更新上一次串口号

    # 扫描串口号
    @staticmethod
    def Serial_Scan_Uart_Port():
        Serial_port_List = list(serial.tools.list_ports.comports())  # 获取串口列表
        now_port = []  # 清空列表数据
        for number in Serial_port_List:     # 循环获取列表中单个元素
            # 单个元素为类 serial.tools.list_ports.ListPortInfo 获取类中的 device 属性,添加在列表中
            now_port.append(number.device)  # 将串口设备添加到列表中
        return now_port  # 返回列表

    # 发送数据按钮槽函数
    @Slot()
    def pBtn_Serial_Send_Data_Slot(self):
        text = self.ui.lEdit_Serial_Send_Data.text()        # 获取输入内容
        try:
            if text and self.Serial.is_open:
                # print(text.encode('utf-8'))
                self.Serial.write(text.encode('utf-8'))     # 发送数据将数据转换成 utf-8 格式发送
        except Exception as error:
            print(error)

    # 打开串口槽函数
    @Slot()
    def pBtn_Serial_Open_Slot(self):
        stop_bit = (11.52)
        check_bit = ('N''O''E')  # 'N' 无校验 'O' 奇校验 'E'偶校验
        choice_serial_port_number = self.ui.cBox_Serial_Port_Number.currentText()
        choice_serial_baud_rate = int(self.ui.cBox_Serial_Baud_Rate.currentText())
        choice_serial_data_bit = int(self.ui.cBox_Serial_Data_Bit.currentText())
        choice_serial_check_bit = self.ui.cBox_Serial_Check_Bit.currentIndex()
        choice_serial_stop_bit = self.ui.cBox_Serial_Stop_Bit.currentIndex()
        # print("open serial", choice_serial_port_number,
        #       choice_serial_baud_rate,
        #       choice_serial_data_bit,
        #       check_bit[choice_serial_check_bit],
        #       stop_bit[choice_serial_stop_bit]
        #       )
        try:
            self.Serial = serial.Serial(port=choice_serial_port_number,
                                        baudrate=choice_serial_baud_rate,
                                        bytesize=choice_serial_data_bit,
                                        parity=check_bit[choice_serial_check_bit],
                                        stopbits=stop_bit[choice_serial_stop_bit])
            if self.Serial.is_open:
                self.ui.pBtn_Serial_Open.setEnabled(False)
                self.ui.pBtn_Serial_Close.setEnabled(True)
                self.ui.cBox_Serial_Port_Number.setEnabled(False)
                self.ui.cBox_Serial_Baud_Rate.setEnabled(False)
                self.ui.cBox_Serial_Data_Bit.setEnabled(False)
                self.ui.cBox_Serial_Stop_Bit.setEnabled(False)
                self.ui.cBox_Serial_Check_Bit.setEnabled(False)
                print("串口已经打开", self.Serial.name)
        except Exception as error:
            if "拒绝访问" in str(error):
                QMessageBox.information(self, "Error""串口拒绝访问", QMessageBox.Yes, QMessageBox.Yes)
            else:
                QMessageBox.information(self, "Error""打开失败", QMessageBox.Yes, QMessageBox.Yes)
            self.ui.pBtn_Serial_Open.setEnabled(True)
            print(error)
        # 判断第一次打开串口之后才开启线程不然,在创建就打开的时候会影响软件打开速度
        if self.Serial.is_open and (not self.Serial_Open_state):    # 串口打开成功
            self.Serial_Data_RX_Thread.start()                      # 开启线程
            self.Serial_Open_state = True                           # 设置打开标志位 防止重复开启线程

    # 关闭按钮槽函数
    @Slot()
    def pBtn_Serial_Close_Slot(self):
        try:
            if self.Serial.is_open:
                self.ui.pBtn_Serial_Close.setEnabled(False)
                self.ui.pBtn_Serial_Open.setEnabled(True)
                self.ui.cBox_Serial_Port_Number.setEnabled(True)
                self.ui.cBox_Serial_Baud_Rate.setEnabled(True)
                self.ui.cBox_Serial_Data_Bit.setEnabled(True)
                self.ui.cBox_Serial_Stop_Bit.setEnabled(True)
                self.ui.cBox_Serial_Check_Bit.setEnabled(True)
                self.Serial.close()
        except Exception as error:
            self.ui.pBtn_Serial_Close.setEnabled(True)
            print(error)

    # 清空接收数据按钮槽函数
    @Slot()
    def pBtn_Serial_Clear_RX_Data_Slot(self):
        self.ui.tB_Serial_Data_RX_Show.clear()

    # 清空发送数据按钮槽函数
    @Slot()
    def pBtn_Serial_Clear_TX_Data_Slot(self):
        self.ui.lEdit_Serial_Send_Data.clear()


if __name__ == "__main__":
    # 初始化窗口系统并且使用在argv中的argc个命令行参数构造一个应用程序对象app
    app = 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.py中代码

import sys
from PySide2.QtWidgets import QApplication
from main_Screen import QmyMain_Screen

# 初始化窗口系统并且使用在argv中的argc个命令行参数构造一个应用程序对象app
app = 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_())

ui_Serial.py中代码

# -*- coding: utf-8 -*-

################################################################################
## Form generated from reading UI file 'ui_Serial.ui'
##
## Created by: Qt User Interface Compiler version 5.15.2
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################

from PySide2.QtCore import *
from PySide2.QtGui import *
from PySide2.QtWidgets import *

import res_rc

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        if not MainWindow.objectName():
            MainWindow.setObjectName(u"MainWindow")
        MainWindow.resize(792457)
        icon = QIcon()
        icon.addFile(u":/image/images/uart_icon.ico", QSize(), QIcon.Normal, QIcon.Off)
        MainWindow.setWindowIcon(icon)
        self.centralwidget = QWidget(MainWindow)
        self.centralwidget.setObjectName(u"centralwidget")
        self.gridLayout_2 = QGridLayout(self.centralwidget)
        self.gridLayout_2.setObjectName(u"gridLayout_2")
        self.verticalLayout_3 = QVBoxLayout()
        self.verticalLayout_3.setObjectName(u"verticalLayout_3")
        self.tB_Serial_Data_RX_Show = QTextBrowser(self.centralwidget)
        self.tB_Serial_Data_RX_Show.setObjectName(u"tB_Serial_Data_RX_Show")
        self.tB_Serial_Data_RX_Show.setMinimumSize(QSize(0160))

        self.verticalLayout_3.addWidget(self.tB_Serial_Data_RX_Show)


        self.gridLayout_2.addLayout(self.verticalLayout_3, 0011)

        self.gridLayout = QGridLayout()
        self.gridLayout.setObjectName(u"gridLayout")
        self.verticalLayout = QVBoxLayout()
        self.verticalLayout.setObjectName(u"verticalLayout")
        self.horizontalLayout_2 = QHBoxLayout()
        self.horizontalLayout_2.setObjectName(u"horizontalLayout_2")
        self.cBox_Serial_Port_Number = QComboBox(self.centralwidget)
        self.cBox_Serial_Port_Number.setObjectName(u"cBox_Serial_Port_Number")
        sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed)
        sizePolicy.setHorizontalStretch(2)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.cBox_Serial_Port_Number.sizePolicy().hasHeightForWidth())
        self.cBox_Serial_Port_Number.setSizePolicy(sizePolicy)
        self.cBox_Serial_Port_Number.setMinimumSize(QSize(900))

        self.horizontalLayout_2.addWidget(self.cBox_Serial_Port_Number)

        self.label_Serial_Port_Number = QLabel(self.centralwidget)
        self.label_Serial_Port_Number.setObjectName(u"label_Serial_Port_Number")
        sizePolicy1 = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
        sizePolicy1.setHorizontalStretch(1)
        sizePolicy1.setVerticalStretch(0)
        sizePolicy1.setHeightForWidth(self.label_Serial_Port_Number.sizePolicy().hasHeightForWidth())
        self.label_Serial_Port_Number.setSizePolicy(sizePolicy1)
        self.label_Serial_Port_Number.setMinimumSize(QSize(600))

        self.horizontalLayout_2.addWidget(self.label_Serial_Port_Number)


        self.verticalLayout.addLayout(self.horizontalLayout_2)

        self.horizontalLayout_3 = QHBoxLayout()
        self.horizontalLayout_3.setObjectName(u"horizontalLayout_3")
        self.cBox_Serial_Baud_Rate = QComboBox(self.centralwidget)
        self.cBox_Serial_Baud_Rate.addItem("")
        self.cBox_Serial_Baud_Rate.addItem("")
        self.cBox_Serial_Baud_Rate.addItem("")
        self.cBox_Serial_Baud_Rate.addItem("")
        self.cBox_Serial_Baud_Rate.addItem("")
        self.cBox_Serial_Baud_Rate.addItem("")
        self.cBox_Serial_Baud_Rate.addItem("")
        self.cBox_Serial_Baud_Rate.addItem("")
        self.cBox_Serial_Baud_Rate.setObjectName(u"cBox_Serial_Baud_Rate")
        sizePolicy.setHeightForWidth(self.cBox_Serial_Baud_Rate.sizePolicy().hasHeightForWidth())
        self.cBox_Serial_Baud_Rate.setSizePolicy(sizePolicy)
        self.cBox_Serial_Baud_Rate.setMinimumSize(QSize(900))

        self.horizontalLayout_3.addWidget(self.cBox_Serial_Baud_Rate)

        self.label_Serial_Baud_Rate = QLabel(self.centralwidget)
        self.label_Serial_Baud_Rate.setObjectName(u"label_Serial_Baud_Rate")
        sizePolicy1.setHeightForWidth(self.label_Serial_Baud_Rate.sizePolicy().hasHeightForWidth())
        self.label_Serial_Baud_Rate.setSizePolicy(sizePolicy1)
        self.label_Serial_Baud_Rate.setMinimumSize(QSize(600))

        self.horizontalLayout_3.addWidget(self.label_Serial_Baud_Rate)


        self.verticalLayout.addLayout(self.horizontalLayout_3)

        self.horizontalLayout_4 = QHBoxLayout()
        self.horizontalLayout_4.setObjectName(u"horizontalLayout_4")
        self.cBox_Serial_Data_Bit = QComboBox(self.centralwidget)
        self.cBox_Serial_Data_Bit.addItem("")
        self.cBox_Serial_Data_Bit.addItem("")
        self.cBox_Serial_Data_Bit.addItem("")
        self.cBox_Serial_Data_Bit.addItem("")
        self.cBox_Serial_Data_Bit.setObjectName(u"cBox_Serial_Data_Bit")
        sizePolicy.setHeightForWidth(self.cBox_Serial_Data_Bit.sizePolicy().hasHeightForWidth())
        self.cBox_Serial_Data_Bit.setSizePolicy(sizePolicy)
        self.cBox_Serial_Data_Bit.setMinimumSize(QSize(900))

        self.horizontalLayout_4.addWidget(self.cBox_Serial_Data_Bit)

        self.label_Serial_Data_Bit = QLabel(self.centralwidget)
        self.label_Serial_Data_Bit.setObjectName(u"label_Serial_Data_Bit")
        sizePolicy1.setHeightForWidth(self.label_Serial_Data_Bit.sizePolicy().hasHeightForWidth())
        self.label_Serial_Data_Bit.setSizePolicy(sizePolicy1)
        self.label_Serial_Data_Bit.setMinimumSize(QSize(600))

        self.horizontalLayout_4.addWidget(self.label_Serial_Data_Bit)


        self.verticalLayout.addLayout(self.horizontalLayout_4)

        self.horizontalLayout_5 = QHBoxLayout()
        self.horizontalLayout_5.setObjectName(u"horizontalLayout_5")
        self.cBox_Serial_Stop_Bit = QComboBox(self.centralwidget)
        self.cBox_Serial_Stop_Bit.addItem("")
        self.cBox_Serial_Stop_Bit.addItem("")
        self.cBox_Serial_Stop_Bit.addItem("")
        self.cBox_Serial_Stop_Bit.setObjectName(u"cBox_Serial_Stop_Bit")
        sizePolicy.setHeightForWidth(self.cBox_Serial_Stop_Bit.sizePolicy().hasHeightForWidth())
        self.cBox_Serial_Stop_Bit.setSizePolicy(sizePolicy)
        self.cBox_Serial_Stop_Bit.setMinimumSize(QSize(900))

        self.horizontalLayout_5.addWidget(self.cBox_Serial_Stop_Bit)

        self.label_Serial_Stop_Bit = QLabel(self.centralwidget)
        self.label_Serial_Stop_Bit.setObjectName(u"label_Serial_Stop_Bit")
        sizePolicy1.setHeightForWidth(self.label_Serial_Stop_Bit.sizePolicy().hasHeightForWidth())
        self.label_Serial_Stop_Bit.setSizePolicy(sizePolicy1)
        self.label_Serial_Stop_Bit.setMinimumSize(QSize(600))

        self.horizontalLayout_5.addWidget(self.label_Serial_Stop_Bit)


        self.verticalLayout.addLayout(self.horizontalLayout_5)

        self.horizontalLayout_6 = QHBoxLayout()
        self.horizontalLayout_6.setObjectName(u"horizontalLayout_6")
        self.cBox_Serial_Check_Bit = QComboBox(self.centralwidget)
        self.cBox_Serial_Check_Bit.addItem("")
        self.cBox_Serial_Check_Bit.addItem("")
        self.cBox_Serial_Check_Bit.addItem("")
        self.cBox_Serial_Check_Bit.setObjectName(u"cBox_Serial_Check_Bit")
        sizePolicy.setHeightForWidth(self.cBox_Serial_Check_Bit.sizePolicy().hasHeightForWidth())
        self.cBox_Serial_Check_Bit.setSizePolicy(sizePolicy)
        self.cBox_Serial_Check_Bit.setMinimumSize(QSize(900))

        self.horizontalLayout_6.addWidget(self.cBox_Serial_Check_Bit)

        self.label_Serial_Check_Bit = QLabel(self.centralwidget)
        self.label_Serial_Check_Bit.setObjectName(u"label_Serial_Check_Bit")
        sizePolicy1.setHeightForWidth(self.label_Serial_Check_Bit.sizePolicy().hasHeightForWidth())
        self.label_Serial_Check_Bit.setSizePolicy(sizePolicy1)
        self.label_Serial_Check_Bit.setMinimumSize(QSize(600))

        self.horizontalLayout_6.addWidget(self.label_Serial_Check_Bit)


        self.verticalLayout.addLayout(self.horizontalLayout_6)


        self.gridLayout.addLayout(self.verticalLayout, 0011)

        self.verticalLayout_2 = QVBoxLayout()
        self.verticalLayout_2.setObjectName(u"verticalLayout_2")
        self.verticalLayout_2.setSizeConstraint(QLayout.SetMinAndMaxSize)
        self.verticalLayout_2.setContentsMargins(0-1-1-1)
        self.Label_Serial_name = QLabel(self.centralwidget)
        self.Label_Serial_name.setObjectName(u"Label_Serial_name")
        sizePolicy2 = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
        sizePolicy2.setHorizontalStretch(0)
        sizePolicy2.setVerticalStretch(0)
        sizePolicy2.setHeightForWidth(self.Label_Serial_name.sizePolicy().hasHeightForWidth())
        self.Label_Serial_name.setSizePolicy(sizePolicy2)
        font = QFont()
        font.setFamily(u"u5e7cu5706")
        font.setPointSize(18)
        self.Label_Serial_name.setFont(font)
        self.Label_Serial_name.setFocusPolicy(Qt.NoFocus)
        self.Label_Serial_name.setLayoutDirection(Qt.LeftToRight)
        self.Label_Serial_name.setTextFormat(Qt.AutoText)
        self.Label_Serial_name.setAlignment(Qt.AlignCenter)

        self.verticalLayout_2.addWidget(self.Label_Serial_name)

        self.horizontalLayout = QHBoxLayout()
        self.horizontalLayout.setObjectName(u"horizontalLayout")
        self.lEdit_Serial_Send_Data = QLineEdit(self.centralwidget)
        self.lEdit_Serial_Send_Data.setObjectName(u"lEdit_Serial_Send_Data")

        self.horizontalLayout.addWidget(self.lEdit_Serial_Send_Data)

        self.pBtn_Serial_Send_Data = QPushButton(self.centralwidget)
        self.pBtn_Serial_Send_Data.setObjectName(u"pBtn_Serial_Send_Data")
        self.pBtn_Serial_Send_Data.setMaximumSize(QSize(8016777215))

        self.horizontalLayout.addWidget(self.pBtn_Serial_Send_Data)


        self.verticalLayout_2.addLayout(self.horizontalLayout)

        self.horizontalLayout_7 = QHBoxLayout()
        self.horizontalLayout_7.setObjectName(u"horizontalLayout_7")
        self.pBtn_Serial_Open = QPushButton(self.centralwidget)
        self.pBtn_Serial_Open.setObjectName(u"pBtn_Serial_Open")
        self.pBtn_Serial_Open.setAutoDefault(False)
        self.pBtn_Serial_Open.setFlat(False)

        self.horizontalLayout_7.addWidget(self.pBtn_Serial_Open)

        self.horizontalSpacer = QSpacerItem(4020, QSizePolicy.Expanding, QSizePolicy.Minimum)

        self.horizontalLayout_7.addItem(self.horizontalSpacer)

        self.pBtn_Serial_Close = QPushButton(self.centralwidget)
        self.pBtn_Serial_Close.setObjectName(u"pBtn_Serial_Close")

        self.horizontalLayout_7.addWidget(self.pBtn_Serial_Close)

        self.horizontalSpacer_2 = QSpacerItem(4020, QSizePolicy.Expanding, QSizePolicy.Minimum)

        self.horizontalLayout_7.addItem(self.horizontalSpacer_2)

        self.pBtn_Serial_Clear_RX_Data = QPushButton(self.centralwidget)
        self.pBtn_Serial_Clear_RX_Data.setObjectName(u"pBtn_Serial_Clear_RX_Data")

        self.horizontalLayout_7.addWidget(self.pBtn_Serial_Clear_RX_Data)

        self.horizontalSpacer_3 = QSpacerItem(4020, QSizePolicy.Expanding, QSizePolicy.Minimum)

        self.horizontalLayout_7.addItem(self.horizontalSpacer_3)

        self.pBtn_Serial_Clear_TX_Data = QPushButton(self.centralwidget)
        self.pBtn_Serial_Clear_TX_Data.setObjectName(u"pBtn_Serial_Clear_TX_Data")

        self.horizontalLayout_7.addWidget(self.pBtn_Serial_Clear_TX_Data)


        self.verticalLayout_2.addLayout(self.horizontalLayout_7)


        self.gridLayout.addLayout(self.verticalLayout_2, 0111)


        self.gridLayout_2.addLayout(self.gridLayout, 1011)

        MainWindow.setCentralWidget(self.centralwidget)

        self.retranslateUi(MainWindow)

        self.cBox_Serial_Baud_Rate.setCurrentIndex(3)
        self.cBox_Serial_Data_Bit.setCurrentIndex(3)
        self.cBox_Serial_Stop_Bit.setCurrentIndex(0)
        self.pBtn_Serial_Open.setDefault(False)


        QMetaObject.connectSlotsByName(MainWindow)
    # setupUi

    def retranslateUi(self, MainWindow):
        MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow"u"Python u4e32u53e3u52a9u624b"None))
        self.label_Serial_Port_Number.setText(QCoreApplication.translate("MainWindow"u"u4e32u53e3u53f7"None))
        self.cBox_Serial_Baud_Rate.setItemText(0, QCoreApplication.translate("MainWindow"u"1200"None))
        self.cBox_Serial_Baud_Rate.setItemText(1, QCoreApplication.translate("MainWindow"u"2400"None))
        self.cBox_Serial_Baud_Rate.setItemText(2, QCoreApplication.translate("MainWindow"u"4800"None))
        self.cBox_Serial_Baud_Rate.setItemText(3, QCoreApplication.translate("MainWindow"u"9600"None))
        self.cBox_Serial_Baud_Rate.setItemText(4, QCoreApplication.translate("MainWindow"u"19200"None))
        self.cBox_Serial_Baud_Rate.setItemText(5, QCoreApplication.translate("MainWindow"u"38400"None))
        self.cBox_Serial_Baud_Rate.setItemText(6, QCoreApplication.translate("MainWindow"u"57600"None))
        self.cBox_Serial_Baud_Rate.setItemText(7, QCoreApplication.translate("MainWindow"u"115200"None))

        self.label_Serial_Baud_Rate.setText(QCoreApplication.translate("MainWindow"u"u6ce2u7279u7387"None))
        self.cBox_Serial_Data_Bit.setItemText(0, QCoreApplication.translate("MainWindow"u"5"None))
        self.cBox_Serial_Data_Bit.setItemText(1, QCoreApplication.translate("MainWindow"u"6"None))
        self.cBox_Serial_Data_Bit.setItemText(2, QCoreApplication.translate("MainWindow"u"7"None))
        self.cBox_Serial_Data_Bit.setItemText(3, QCoreApplication.translate("MainWindow"u"8"None))

        self.label_Serial_Data_Bit.setText(QCoreApplication.translate("MainWindow"u"u6570u636eu4f4d"None))
        self.cBox_Serial_Stop_Bit.setItemText(0, QCoreApplication.translate("MainWindow"u"1"None))
        self.cBox_Serial_Stop_Bit.setItemText(1, QCoreApplication.translate("MainWindow"u"1.5"None))
        self.cBox_Serial_Stop_Bit.setItemText(2, QCoreApplication.translate("MainWindow"u"2"None))

        self.label_Serial_Stop_Bit.setText(QCoreApplication.translate("MainWindow"u"u505cu6b62u4f4d"None))
        self.cBox_Serial_Check_Bit.setItemText(0, QCoreApplication.translate("MainWindow"u"u65e0"None))
        self.cBox_Serial_Check_Bit.setItemText(1, QCoreApplication.translate("MainWindow"u"u5947u6821u9a8c"None))
        self.cBox_Serial_Check_Bit.setItemText(2, QCoreApplication.translate("MainWindow"u"u5076u68c0u9a8c"None))

        self.label_Serial_Check_Bit.setText(QCoreApplication.translate("MainWindow"u"u6821u9a8cu4f4d"None))
        self.Label_Serial_name.setText(QCoreApplication.translate("MainWindow"u"WSP-u81eau5236Pythonu4e32u53e3u52a9u624b"None))
        self.pBtn_Serial_Send_Data.setText(QCoreApplication.translate("MainWindow"u"u53d1u9001u6570u636e"None))
        self.pBtn_Serial_Open.setText(QCoreApplication.translate("MainWindow"u"u6253u5f00u4e32u53e3"None))
        self.pBtn_Serial_Close.setText(QCoreApplication.translate("MainWindow"u"u5173u95edu4e32u53e3"None))
        self.pBtn_Serial_Clear_RX_Data.setText(QCoreApplication.translate("MainWindow"u"u6e05u7a7au63a5u6536u6570u636e"None))
        self.pBtn_Serial_Clear_TX_Data.setText(QCoreApplication.translate("MainWindow"u"u6e05u7a7au53d1u9001u6570u636e"None))
    # retranslateUi


res_rc.py中代码

# Resource object code (Python 3)
# Created by: object code
# Created by: The Resource Compiler for Qt version 5.15.2
# WARNING! All changes made in this file will be lost!

from PySide2 import QtCore

qt_resource_data = b"
x00x00x0bx81
x00
x01x08>xx9cxedx9dmx8cx1bGx19xc7}xbdJ
xc7xb7|xa3xfdDx91*x91~x0dxa2 xa1B>
 J /Mx9b6isj|xb6xcfxf6xday
xb9x84x94xb4IJxce=xdb=*x22xa0x08x09x89
/x91xe0x03xa0x06x0aExa2Px81txd2%x95Z
)x12xb4x0dp:@x11H-oxe2x82xe8x89xfb
`{x98xffxfaxe5vxd7xfbxeax9dxf53xb3x9ex07
xfdx8b&gxafx9fy~xbb3xbbxb33xcfd2
Sxfcx7fxb5Zx86xffxf7xc3x99xfb>0x95xf9`
&x93xb9x8fx8bxffx13xfexd1xfcxf7xaeMe&xc1
x8e?xcf>Rmxb0xfdFxbd}xc6hxb6xbfQ
itxaeVxeaxadkxd5Fxe7xf7x95Fxfb=.
xe6xa1xf7xaaMxfex19xfeY|x07xdfxc51p,
x1cx93xba^xdax86xadxb4xc8>xc4xf9|xe3jx13xbfx81xdfxc2
oxe2xb7xa9xeb?x89Vxadxb3=xddxebxba}3
Axd6aux13xbexc0'xeaxb8xa4xd9*Mxb6x97
_wWxx1bxbe!x01s/xddx86x8fxf0x95:
^ixb0Jx9dxed4x1axedexcexfcx1fx12xb0x8d
$xf8x0cxdfQx07xea8xaafFx93}x8exdfx83
xbdLxcdPx98x]P'xeaxb8xcanxe5zxeb
xd1Jxa3xb5Jxce+1xb5Vx8dx06;Dx1dg
xd9xccxx9exedxeb>sQxf3x19[{px0du
xa6x8e;xb5x19xcblx17xef'x7fBxcex83Hxa8
;b@xcdaxdcVxabxb1;xf8xbdxd1x0bxd4xf1
x97Ex88x05bBxcdex1cxc6xdbxbex03xbcxcek
xd41x97Pkx88x0d5x9f$xad;FGx1eg
xa9x85x18Qsx12mxd5ev?oxe3nPxc7
Vx15!Vx88x1957x11fxd4[Exeaxxaa
*xc4x8ex9a_x1cxabxd6xdbx97xa9cxa8xbax10C
jx8eQx8dxdfxcbxdeYmv^xa2x8e]Zx84
Xx22xa6xd4x5cxc3Xxb5xf6xfexddxe9x1exc3xa3R
kuxa1xc9xeexa2xe6xebgxa5:xbbxb7xdaxe8xbc
Ex1fxabtx0axb1Ex8cxa99xbbYxa5xbexb5x93
xfbxb8Nx1dxa3x09xd0:bMxcdxdbj8'5
xfbxf1x9ex03xb2xb4x03xe8x93tx9b?~!xe6xb8
xd7xa2dx8f{R}xafGxa9xd6*xe5sx81~
xc6xa3x17x18x90xb0xd7c;xd2hxdccD*x8f
xe9x96x9fxfex0dxcbxe7xealxeexf0,;xf6x85x8f
xb2xb9xc7x8exb0|xf6YVx5cxx85xdcxb78x1a
xd7X1xdeKPxd7uTx15Oxfexc8dxeexa5
|xee9fxd4xfeMxeexe7xa8x1axc7;#Uxdf
xe3xe5x8f=xe3xcbxbexafxdcxd1xe3xe4xbex8e*xb0
Ix92xbdxaaxefxefxd1xb6x87axdfx97xca}AR
xf3x07zxf3vxc8xeb7x8arGrx91xf8xcfx1d
xc9x92xfbx1cKx82xe7x11anZExe19[xd9
xfdx0fDxe2x0fx19x97xde%xf7;x86xd6Dxce'
Tyxaefxf9xe2x9f#xb37xfbx80xd3?'xf7
=x8exc0Lx08xfbexb6x8bxba.xb1xf8x9fxffxdd
hxfcOxfex98xdcxf7xd8xe7x80x80xb9xe5xaaxcfxcf
7x16xff5x12xffxf2x85urxdfxe3x0axecb]
xfbXx97#A=xe2*xffxe4xb9HxecsOx18
xe4>x8bRx9cuFiYx93x851xbfHmxff
xa9x97xc9}x16&xcepxa4kxbfxc1x0ex91xfb.
Pxc53xbfx0cxc5~xbexfcmr_Ex0bxebj
#_xfb)|xaf[:xbbxc2xdbxf6xa2+xf7xec
xc1xcfxf3{xbexabxe4>&xa3xd6jxa4kx1fxeb
xefxc9}NNnxe7x00xb5OI+Jxfex81T
xe5^xd0xfcxbbxe2Lxc3xb1g;xc9}xd5xfcx13
:x07x82sxd1x989kxa8xfdxd4xfcx13x11xd8x06
xf1W1xd7x92xe6x1fN`xebxdbxf67xd9^j
x1f5xffx84xe5x93x9bxcexcc]Gxedx1fx11xffB
xbeIxeexd78x04xc6x9exd7x7fxa3}x9bxda?*
xfexe6xb8xefx919Vxbexf0Grxffx92xe5xdfxd9
pxbdxf6xeblx0fxb5oxd4xfcxcdqxa0xfdx0fxb0
xe2xe9Wxc9}Lxf4x1cpxc9Uxdbxcbxa7Kxee
x1b5xffAx7fPxfc&xb9x9fIx09xac]xda~
x19r)Kxc3xdfxecx0ffO2xe3xd2xdfxc8xfd
M@7xadxecx91xc7x5cx02x9fxa4xe3xdfx7f/P
zxeax1axb9xcfxa2excd]x8fx5cxf6xd4xfePxf2
x0fs>xccWxaex90xfb-R`nyxeeSr
^xb7Hxfexa5/xfex9axcd=zxd8xf7x1cxc8g
/xb0xcaxd2&xb9xffbxf8oxcfx13xefxedx9bA
xeex13%x7fxfcxbbQxdb`x85xc2W|xcfx01x9c
#xe5xa7x7fK^x87xf8xeax5cxb7xdcxfb%xb9g
x8aT*x9dxbbxc1xe6x0e=xecxcaxbfxafxe2x99xd7
xd8xdc#x07xfcxe7x08xa9?W`x13xecxb1xafx91
x04xbex8cE`xe6zx9fxefxe0x0faxdeh>xd7
xf0x7fFxcc/x93xd7)x8exfaxfbdQxfb1x0e
x15x0a_xf5~xcesxe1?8gNxbfxcax9fx01
>xebxfdxddxc7x0bxe6x1ax03xeaxfax8dxa2xfex1ei
xd4~$)xe3xcbxefxb2xdcxd1x8axffsxbex0fx7f
xf3x18x97xfenxaex11xf7x1e3xfcxb49xbfx90xba
xaex91c3xd8x1fx8fxdex97$x84xe7xf6xecxc1=
xc1xe3
xf4x19~?xf7x0b!xfcxa1xd2xb97x95xe6x1fb
?x5ceTx9fxbdxc8x8cxdax7fx5cxbfxab
xf9xabxadxe2xc2xcfxf8xbdxf7xc7xbdxdbxfbxe3xdfxf7
xfdxbexe6xafxaex0axc5x17xbdxdbxfbxc3Gyxbbxf0
vxe014x7f5x85xd8zx8exbdExx17xa3xf9
xdbxa4xcc3x80+xff}x9f4xfbx83(xc7x89xc3
x1fx9fxb5xcamlxc9*x9c#xd4qxf3cxdfx1b
x03xa0xf6cdxfexa3xc47x0ex7fxbfxe7L7xc9
xccxdfx1cxfbShxfcWxf3x17,x93xbd:xefx7f
4x7fxd1xeax5cUxe9xfdxafxe6/V`xafxd2xfc
x0fxcd_0x7fxce^xa5xf9_x9axbfXx81xbdJ
xf3?5x7fxb1x02{Uxe6x7fx17xe6xbfxe6x1exe3
}x9f`xf3xd5xefjxfexd1xb5xa9xcaxfax8f0{
wDx19wxd3xfcxa1xedxf5x1f2xafxff*x9exfa
ixf8Xx9f]xd1xfcCxcaxbaxfeKxdaxf5x9fK
[,xfbxf0xdexd0xb1xc6x1cx1fxcd?,xffxedxf5
x9fxb2xaexff6x9exfdKxa4XcxbeGxd2xfcxd1
xcfDx91xacxfcxadxebxbfexcdxffxe0xf6~-H
Ixf3Ox89lxf9x1fdxcdxffbOxedxb3xb0xbaxc7xd8xff
x19xa6xfaxfexef}x15x8a_x0fxf7x0ex89x7fx8exda
WQx8axbbxffxbbxd9x06,xb3]xd4xf5x10%xe4
x7fxf2cx8fxbfSxfb(R`x17x97xbfyx0e4
xda/Pxd7Ex84x8axa7^xf1x7fx87xc4xffNxed
xa30xf6x9cx99x08xf6xb0Zx8dxddxc1x8fxb9F]
'x11xf2xdcxfb+Uxcf{xed50x13xc5x1fxc6
xef#x0fHPxafxd8Bxde 7xfexf8wjxdf
x84x89xb3x12xc9xbeo2xcfx13x8fx22xe4x0axb1xdd
xf3)x9exc7xdd*xebxbcxee$x8cxf7+7xa8xeb
x18[Kxffx1bxdcx0bx9axf7|xbcLxeex93x00x81
Mx92xecaxd5ev?u=Ehxfexc4x0fL
xfexf8x7fj_Dx09lx92xe6x0f3xeaxadx22u
]E(Mxf7|`2x0exf6}xabxd6xdbx97xa9
xebx1cWxb2xaexcdx88*xb0x18'xfbxc19xd0xec
xbcD]xf7Ix17x18Pxb0x87xf1gxcc;xd3xf2
x9eXMxb5Vxc1x80x8a?xacZ{xffxeejxa3
xf3x16},&Kx88xf9Bx93xddExc9xbeoxa5
:xbbx97xfbxb4Nx1dx93x09xd2:bNxcdxddj
x95xfaxd6N}x0ex8cx87=bMxcdxdbxcdpN
xeaxbe 9!xb6xb2]xf7NCx9fxa4xefx09x93
Pkx15xf7Zxd4|xc3x18xeeIxf5xb3xa18!
x96xd4xf7xf9xa3Xx1axc6x88xa8E5xb6#xcaxd2
2VLxa1qx8fxe9&ex/x91x8axf7x86xe3
xe2xcec5xaew9xe3xb4xb4xccx1fHRIxbf
xbfxa7xb6xdex06xe4Xxd3x16xcf
x90xbfxb4x97xafXx86x9cxd57xe1x8bWNUm
xc9x1axf2x98#x97}wlxd1xdcxc7$xc9xbdl
6xf1x1bxf8-xfcxa63x87xba69x0cxfbx1aa
o+xecoxd6m':Wxf1xccexeewxeaxbf
xdfqw?xdcxdexbex98xfdxfdxf1xfaxfbdQxd7
+x09cxa6me2xf70x8bmxffxa5xffgxdb
x07VPx9ex1ex14;xddC-xda>nxf9xc2xad
nyxa6_xaeuxcbSxf6xafx0fx0exb0xd5/xf7
x0e0xc8Wxbcxc3vxb8xc1x01kxfdxf2xd4xb6xf3
x96xcauxb6xcbx8bxb6xc3xf7~xc0x92x0eyx87xed
xf0xbdx1fXxd9.Oxdb~xaexf7x83x8ehvxac
xe5xc5xe1rxcbZxdemsxc7txc8x96x9dyxc7
pxf9x96xb5

qt_resource_name = b"
x00x05
x00p7xd5
x00i
x00mx00ax00gx00e
x00x06
x07x03}xc3
x00i
x00mx00ax00gx00ex00s
x00x0d
x01rx05xbf
x00u
x00ax00rx00tx00_x00ix00cx00ox00nx00.x00ix00cx00o
"

qt_resource_struct = b"
x00x00x00x00x00x02x00x00x00x01x00x00x00x01
x00x00x00x00x00x00x00x00
x00x00x00x00x00x02x00x00x00x01x00x00x00x02
x00x00x00x00x00x00x00x00
x00x00x00x10x00x02x00x00x00x01x00x00x00x03
x00x00x00x00x00x00x00x00
x00x00x00x22x00x01x00x00x00x01x00x00x00x00
x00x00x01x80xdex9exc2 
"

def qInitResources():
    QtCore.qRegisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data)

def qCleanupResources():
    QtCore.qUnregisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data)

qInitResources()

3.2.4、python代码打包

  将python打包成不同的平台的软件的时候最好在不同的平台下打包,我这里在windows下面打包,使用的插件是pyinstaller,安装方法和pyserial一样的,我们直接在pycharm中使用终端来实现打包。

串口

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

串口

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

串口

PS C:WindowsSystem32> set-executionpolicy remotesigned
Set-ExecutionPolicy: Access to the path 'C:Program FilesWindowsAppsMicrosoft.PowerShell_7.3.1.0_x64__8wekyb3d8bbwepowershell.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:QTqt-uart-pythondistimagesuart_icon.ico main.py

串口

 

串口

3.2.5、源码地址

  源码地址:https://gitee.com/wsp6666/qt-uart-python.git

审核编辑 :李倩


打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分