文章目录

  • python上位机开发经验总结01
    • python变量与文件的处理
      • 全局变量与局部变量
      • 文件间的变量处理
    • threading模块使用经验
      • 管理线程
      • 定义线程
    • tkinter使用经验
      • tkinter显示视频
      • Frame的使用以及各种摆放方式
      • PanedWindow配合LabelFrame
      • 下拉框与输入框
        • 下拉框
        • 输入框
      • tkinter.after()实现定时器
      • 动态的显示:StringVar()
      • 带滑条的Text
      • 好看的字体设置
      • tkinter修改关闭窗口操作
    • 其它零零碎碎的经验
      • OpenCV实现点击'X'关闭窗口
      • 打包文件
        • pyinstaller错误:RecursionError: maximum recursion depth exceeded

python上位机开发经验总结01

魔方机器人比赛中使用python写了上位机,总结一些经验待以后翻看。主要分4个方面,python变量与文件的处理,threading多线程模块使用经验,tkinter使用经验,其它零零碎碎的经验

python变量与文件的处理

主要是单文件内变量的处理经验与多文件内变量的处理经验。

全局变量与局部变量

python中的局部变量产生在函数内,在函数返回之后被释放,可以(但不建议)与全局变量同名。全局变量只能在模块(文件)级别使用,不能跨模块使用。

python中的全局变量在使用时有两个注意

  1. 全局变量需要在模块级别定义
  2. 在函数中修改全局变量时需要先用修饰词global声明

当函数中没有对变量作global修饰时,有两种情况

  1. 函数中只对变量作读取操作,则可以直接读取在模块级别定义的同名变量
  2. 函数中对变量作了写(修改)操作,则认为此变量为局部变量,即使变量与模块级别定义的变量同名,也会新建局部变量而不会去修改同名变量

如以下的例子

flag = 1def false_reset_flag():flag = 0def right_reset_flag():global flagflag = 0false_reset_flag()
print(flag)
right_reset_flag()
print(flag)

以上程序的执行结果为

1
0

并且,把变量传入函数之后进行修改也是不可行的,python会将传入的变量复制作为局部参数。但是如果在程序中需要很多flag(标志变量),那么逐一定义并修改全局变量是非常不优雅的,我们可以采用传入实例,调用实例中的函数修改自己的变量的方法。终究,python还是面向对象的语言。如以下例子

flag = 1class FlagClass(object):def __init__(self):self.flag = 1def reset_flag(self):self.flag = 0def false_reset(x):x = 0def reset(x):x.flag = 0false_reset(flag)
print(flag)
flag_class = FlagClass()
reset(flag_class)
print(flag_class.flag)

以上程序的执行结果为

1
0

文件间的变量处理

上面其实已经提到了,python最优雅的编程方式是面向对象编程,模块(文件)间的处理也是一样。最好把每个模块中的东西全部封装成类,实现模块之间的解耦。但是有的时候(尤其是完成一个特定的算法和工作流程)面向过程的编程更为方便,这时候我们就需要去处理模块之间的很多flag(标志变量)了。

首先我们要明确一个核心思想:模块之间是不能相互写(修改)变量的。如果从其它模块import了某个变量,实际上是把这个变量复制过来,作为一个本模块内的“局部变量”。所以我们模块间修改变量的策略也是和封装成类差不多:把一个模块看成一个庞大的实体,在模块内定义修改自己变量的函数,在其它模块中调用这个函数。如以下例子

# module1.pyflag = 1def reset_flag():global flagflag = 0# 下面是需要调用的函数,里面会修改flag的值来标志有没有完成
......
# module2.pyfrom module1 import flag, reset_flag
print('flag')if flag:print('动作完成,重置标志')reset_flag()
from module1 import flag
print('flag')

运行module2得到的结果如下

1
动作完成,重置标志
0

threading模块使用经验

threading是python提供的在底层的_threading基础上封装好的使用方便的多线程模块,但是这个模块并不太完善。

之所以说它使用方便,是因为它可以方便地创建线程,且线程中可以直接修改全局变量(像上面说的一样用global声明即可);说它不方便是因为它没有办法很好地管理线程(我的意思是很简单方便,有能力自己设计机制管理的大佬请忽略),如果你的线程中运行的是一个死循环,那么一旦线程开始,就处于失控状态,再也无法结束。

分享我本次使用threading模块的2个经验。

管理线程

下面是我在本次工程中管理线程的经验,虽然并不是一个非常优雅的方法,但是还算好用。核心思想是定义一个线程的flag,定义成全局变量还是定义在类里可以根据实际情况决定。把死循环改为一个带判断的while循环,那么结束线程时只需修改flag的值即可。如以下例子

class COM(object):def __init__(self, port, baud):self.port = portself.baud = int(baud)self.com = serial.Serial()self.recvBuffer = Queue(maxsize=10)self.__recv_running = True  # 标志接收线程是否运行self.recvThread = threading.Thread()def open(self):try:self.com = serial.Serial(self.port, self.baud)except Exception:print('Open COM failed: {}'.format(self.port))def close(self):if self.com is not None and self.com.isOpen():self.com.close()self.stop_receive()def isOpen(self):return self.com.isOpen()def activate_receive(self):def receive_loop():while self.__recv_running:temp = self.com.read()if temp in serial_dict:self.recvBuffer.put(serial_dict[temp])time.sleep(0.02)self.recvThread = threading.Thread(target=receive_loop)self.recvThread.start()def stop_receive(self):self.__recv_running = False

以上是我在本次工程中定义的串口类,其中接收串口数据就是用了单独的一个线程。在类中我定义了__recv_running变量来标志接收线程是否运行,在线程的target函数中每次循环都取判断__recv_running的值,需要结束线程时只需调用stop_receive修改__recv_running的值,那么接收线程执行完当前循环就会自动结束。

当然你应该发现了,修改标志变量值之后需要等待线程中本次循环结束,线程才能结束,无法强行杀死线程。一方面,这样结束线程可能效率不高;另一方面,这样结束线程对程序的冲击最小。

定义线程

在定义线程时,最好把线程的函数定义在启动的函数内,这样程序的可读性有很大的提高。这一点在上面的代码中就可以看出,这里我把它截取出来

def activate_receive(self):def receive_loop():while self.__recv_running:temp = self.com.read()if temp in serial_dict:self.recvBuffer.put(serial_dict[temp])time.sleep(0.02)self.recvThread = threading.Thread(target=receive_loop)self.recvThread.start()

tkinter使用经验

这次的工程中我用tkinter绘制了一个界面,先上图

如果你还没有学习tkinter,并且时间充裕的话,我建议你使用pyQt。tkinter虽然上手非常快(从学开始到编写完这个界面我花了2天),但是它所有的排版都需要你手动敲代码,相比于pyQt的画图而言,就显得太不人性化了。

本次工程中,关于tkinter的资料搜索量是最大的,总结了以下几点经验。

在这次的工程中我参考了以下网页:Python 实现串口调试助手

tkinter显示视频

这个问题面向百度和CSDN搜索了很久,没有一个非常好的解答。最后在python摄像头视频显示到TK窗口改良版找到的代码基础上改良了一下,很好地实现了视频显示。因为找到的代码是需要付积分下载的,原本我并不想放出源码,但是抱着python全部开源的精神,在此我放出源码并配上讲解,希望各位帅哥美女留下一个赞再走。

import cv2 as cv
from PIL import Image, ImageTk
import threading
from queue import Queue# 放置画布
canvases = []
for i in range(4):canvases.append(tk.Canvas(FRAME_cameras, bg='#ffffff', height=240, width=320))
x = [0, 0, 1, 1]
y = [0, 1, 0, 1]
for i in range(4):canvases[i].grid(row=x[i], column=y[i], sticky='nsew')# 下面的代码解决视频流的显示和关闭
show_cam_flag = Truedef stop_show_cam_thread():"""shut down the show_cameras thread:return: None"""global show_cam_flagshow_cam_flag = False# show camera flows
def show_cameras():def cc():imgQueue = Queue(maxsize=10)  # 使用队列存储读取的图像,解决视频闪烁的问题global show_cam_flagwhile show_cam_flag:for i in range(4):ret, frame = cameras[i].read()cv_image = cv.cvtColor(frame, cv.COLOR_BGR2RGB)image = Image.fromarray(cv_image)image_file = ImageTk.PhotoImage(image)imgQueue.put(image_file)canvases[i].create_image(0, 0, anchor='nw', image=image_file, tags='c1')if imgQueue.full():imgQueue.get()  # 队满则出队一个,防止内存占用太大程序崩溃time.sleep(0.04)del imgQueueshow_cam_flag = True  # 关闭视频流后重新置一,避免无法再次打开t = threading.Thread(target=cc)t.start()

核心的显示思想是使用Canvas来显示图片(因为用Label不断刷新图片,内存会过大,程序会崩溃),用单独的一个线程不断地读取摄像头并刷新Canvas显示的图片。因为OpenCV读取的图像格式没有办法直接显示,所以用其它的库处理了一下。

需要注意的是,如果对显示的图片不加以保存的话,每次刷新图片时之前的图片就会被释放,导致显示的视频闪烁。最早找到的代码把图片保存在了字典中(我不太懂为什么要这样操作,可能是试出来的吧),这样做显然是很不优雅的,而且不定期进行删除操作,一定会导致内存过大程序崩溃(和Label就没有区别了)。所以我定义了一个队列imgQueue来存储图片,每个循环都会检查队列并处理溢出的图片,最终在关闭视频流时删除这个队列。这应该是非常优雅的方法了,欢迎各位交流更好的方法。

Frame的使用以及各种摆放方式

这一次我主要使用了tkinter的三种排版方式:tkinter.pack(), tkinter.grid(), tkinter.place()。tkinter的排版还是比较麻烦的,全部使用place肯定非常不优雅。想要整齐的排版,就需要多建立一些Frame,然后3个函数配合使用。这里贴上我的设计

具体怎么写就不赘述了,提几点小建议

  1. 在新建完Frame之后直接放置,这样排版和实现功能的代码可以分开
  2. 在开始绘制的时候再新建Frame,否则写多了容易找不到
  3. 另外,注意实例的命名格式,个人使用的是全拼大写开头,小驼峰表征位置和功能,例如FRAME_leftENTRY_releaseTEXT_status

PanedWindow配合LabelFrame

个人认为这些划线框让界面看起来更有条理,它们的实现方式其实也非常简单,如标题一样用PanedWindow配合LabelFrame即可。可参考如下代码

PAN_com = tk.PanedWindow(FRAME_r_up, orient=tk.VERTICAL, height=180)
PAN_init = tk.PanedWindow(FRAME_r_up, orient=tk.VERTICAL, height=180)FRAME_com = tk.LabelFrame(PAN_com, text="串口设置")
FRAME_init = tk.LabelFrame(PAN_init, text="转换初始化")PAN_com.add(FRAME_com)
PAN_com.pack(side=tk.LEFT)
PAN_init.add(FRAME_init)
PAN_init.pack(side=tk.RIGHT)

然后把要加的东西加到LabelFrame里就行了,即上面的FRAME_comFRAME_init里。

下拉框与输入框

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vQuEECl8-1601652622895)(./pictures/PanedWindow.png)]

还是这张图,里面的下拉框和输入框是不是也很简介美观,下面是实现它们的方法。

下拉框

tkinter.Combobox()个人认为是不大好用的,我使用了tkinter.ttk中的Combobox(),可以这样实现

from tkinter import ttkport_list = list(list_ports.comports())
serial_com_list = []
# varPort = tk.StringVar()
COMBO_com_com = ttk.Combobox(FRAME_com, width=8, height=2, justify=tk.CENTER)
for port in port_list:tmp_port = list(port)serial_com_list.append(tmp_port[0])
COMBO_com_com.grid(row=0, column=1)
COMBO_com_com['values'] = serial_com_listdef refresh_com():global port_list, serial_com_listport_list = list(list_ports.comports())serial_com_list = []for port in port_list:tmp_port = list(port)serial_com_list.append(tmp_port[0])COMBO_com_com['values'] = serial_com_listFRAME_com.after(500, refresh_com)refresh_com()

这一段代码实现了COM:后面的下拉窗口,可以动态显示可用的串口,每500ms刷洗一次。后面还会讲使用tkinter.StringVar()实现动态显示的方法。

输入框

输入框使用tkinter.Entry即可,读取时使用tkinter.Entry.get(),设定默认值可以使用tkinter.Entry.insert(),例如

ENTRY_init_release = tk.Entry(FRAME_init, show=None, width=4)
ENTRY_init_release.insert(0, 25)
ENTRY_init_release.grid(row=0, column=1)

tkinter.after()实现定时器

tkinter.after()函数实际上是等待一段时间后执行一个函数,但是可以通过函数本身的嵌套去实现一个定时器的功能,在stringVar()中一起介绍

动态的显示:StringVar()

有时候你会想在界面上显示一个实时变动的东西,这时候你就需要一个变量去放置你要显示的东西。我使用了一个很好用的类tkinter.StringVar。下面给出使用tkinter.after和tkinter.StringVar实现计时器的方法

global time_count
run_time = -1def start_time_count():"""开始计时界面的定时刷新,此定时器可用time_count来停止:return:"""global time_countglobal run_timerun_time += 1var_time.set('%.2f' % (run_time / 100))time_count = FRAME_time.after(10, start_time_count)PAN_time = tk.PanedWindow(FRAME_r_down, orient=tk.VERTICAL, height=100, width=364)
FRAME_time = tk.LabelFrame(PAN_time, text="运行计时")
PAN_time.add(FRAME_time)
PAN_time.pack()
var_time = tk.StringVar()
LABEL_time = tk.Label(FRAME_time, textvariable=var_time, font=('Arial', 40), fg='red')
LABEL_time.pack()
var_time.set('0.00')
run_time = -1

可以通过函数start_time_count()来启动计时器,使用FRAME_time.after_cancel(time_count)来结束。

带滑条的Text

如果你觉得创建Text再创建滑条太麻烦,那么可以像我一样使用tkinter.scrolledtext中的同名类来创建带滑条的Text窗口。可参考如下代码

import tkinter as tk
from tkinter import scrolledtext# FRAME_status
FRAME_status = tk.Frame(FRAME_r_down)
FRAME_status.pack()
TEXT_status = scrolledtext.ScrolledText(FRAME_status, width=49, height=8, font=('Consolas', 10))
TEXT_status.pack()
TEXT_status.insert(tk.END, 'Welcome to use Beihang Robotics CubicRobot\n')
TEXT_status.see(tk.END)

其中TEXT_status.see(tk.END)可以使Text窗聚焦在底部

好看的字体设置

tkinter几乎所有地方都可以设置字体,例如以下:

LABEL_time = tk.Label(FRAME_time, textvariable=var_time, font=('Arial', 40), fg='red')
BUTTON_start = tk.Button(FRAME_main, text='开始', font=('黑体', 16), width=10, height=2, command=main_start)
TEXT_status = scrolledtext.ScrolledText(FRAME_status, width=49, height=8, font=('Consolas', 10))

在此推荐一种好看的英文字体,适合用在提示窗信息中:Consolas

tkinter修改关闭窗口操作

有时候为了保证程序的正常退出(如需要手动关闭线程),需要修改点击窗口右上方’X’号时触发的函数。tkinter中点击’X’产生的事件为'WM_DELETE_WINDOW',想要修改可以参考如下方法

# 重写关闭窗口函数,先关闭线程再关闭窗口
def terminate():# 关闭视频流线程stop_show_cam_thread()# 若串口打开,关闭串口try:com.close()except NameError:pass# 等待线程关闭再关闭窗口,实测有效root.after(1000, root.destroy)root.protocol('WM_DELETE_WINDOW', terminate)

其它零零碎碎的经验

OpenCV实现点击’X’关闭窗口

一般来说OpenCV都是通过按键盘来关闭(waitKey),但是这样的方法放在一个成熟的GUI中就有失优雅,于是我在Stack Overflow上找到了解决方法,可以看以下例子,也可以参见网址OpenCV Python: How to detect if a window is closed?

total_frames = 50
cv2.cv.NamedWindow("Dragonfly Simulation")
cv2.cv.StartWindowThread()
for i in range(total_frames):# do stuffimg_name = # somethingimg = cv2.cv.LoadImage(img_name)cv2.cv.ShowImage("Dragonfly Simulation", img)cv2.cv.WaitKey(2)
cv2.cv.DestroyWindow("Dragonfly Simulation")
cv2.cv.WaitKey(1)
# rest of code

以上是原网址的代码示例

while get_points_cnt < 9:cv.imshow(get_points_window, image)cv.waitKey(50)if cv.getWindowProperty(get_points_window, 0) < 0:TEXT_status.insert(END, 'Window shut down before finishing setting sampling points\n')TEXT_status.see(END)return

以上是我本次使用的代码,简单地说就是每次刷新图像的循环都对cv2.getWindowProperty(window_name, 0)进行判断,如果窗口已经关闭,这个函数的返回值为-1,如果窗口已经关闭,则打断循环即可

打包文件

写完的代码可以用pyinstaller打包成exe文件,可以让你的程序在没有python环境的电脑上运行。这里只记录一个常用的打包方式

pyinstaller -F -i logo.ico -w main.py -p sub1.py -p sub2.py

-F是指打包成单独的exe文件,而不是很多dll依赖文件的文件夹。

-i指exe文件的图标

-w指生成窗口程序

-p后是子模块

pyinstaller错误:RecursionError: maximum recursion depth exceeded

打包程序是遇到了这个问题,意思是递归深度超过限制。在Stack Overflow上找到了解决方法:pyinstaller creating EXE RuntimeError: maximum recursion depth exceeded while calling a Python object

大概意思就是,使用的某一个包在疯狂递归,导致超过了python的栈深度限制。解决方法是首次运行pyinstaller命令,提示出错后,生成了一个module_name.spec文件,记事本打开这个文件,加上下面两行即可

import sys
sys.setrecursionlimit(5000)

这里的5000指迭代深度,如果不够可以设置得更大

python上位机开发经验总结01相关推荐

  1. “Xilinx ZYNQ+TCP通信+Python上位机”实现实时视频传输系统

    笔者在CSDN的第一篇万字长文,请多多支持. 本文是笔者的公众号 IC设计者笔记 文章的转载.很多优质原创内容都会第一时间发布在公众号,欢迎关注公众号,一起交流学习.公众号后台回复"ZYNQ ...

  2. python3中利用serial模块实现单片机与python上位机的通信(串口调试助手)

    1.指标:    python上位机向单片机发送字符,单片机如果收到的字符为'1',则点亮灯1,如果收到的字符为'2',则点亮灯2:单片机若接受到字符,读取字符后,向python上位机发送字符(1-& ...

  3. python上位机开发实例-python上位机

    广告关闭 腾讯云双11爆品提前享,精选热门产品助力上云,云服务器首年88元起,买的越多返的越多,最高满返5000元! 若python上位机接受到的字符为"1',则print出ok,如果字符是 ...

  4. 用python做一个上位机串口通信_【教程】简易Python上位机之LED控制

    电子爱好者应该不会对"上位机"这个词感到陌生,毕竟或多或少有过接触.但若是说到上位机的开发的话,大家就不一定熟悉了.很多电子爱好者完全没有接触过上位机的开发工作,他们真的没有相应的 ...

  5. python led屏控制_【教程】简易Python上位机之LED控制

    电子爱好者应该不会对"上位机"这个词感到陌生,毕竟或多或少有过接触.但若是说到上位机的开发的话,大家就不一定熟悉了.很多电子爱好者完全没有接触过上位机的开发工作,他们真的没有相应的 ...

  6. C# / VB / LabVIEW / VC / Python 上位机使用S7-TCP协议与西门子PLC进行网口通信的教程 (Win/Linux)

    现在越来越多的项目开始使用上位机了,在上位机实现数据存储.曲线绘制时,使用高级语言自行开发程序,比各种组态软件更加自由,更加强大.在进行上位机软件开发时,第一步就是要跟PLC取得通信,能够读写PLC内 ...

  7. Python上位机软件图形界面实战——PyQt

    转载:https://blog.csdn.net/qq_25939803/article/details/97894219 文章目录 引言 1 环境配置 2 新建一个软件窗口 3 QtDesigner ...

  8. python 上位机直接与西门子变频器建立通信

    利用python直接读取西门子变频器参数,省去变频器与PLC的连接,代码如下. 硬件连接:一根网线连接电脑与变频器,变频器必须要有PN口. 电脑的网络需与变频器的网络ip在同一个网段.如变频器为192 ...

  9. python上位机界面设计_用Python写界面--上位机开发

    Python真的可以说是无所不能,上到人工智能.图像识别.下到控制电机.爬虫.数据处理,前不久发现Python还可以做界面,虽然比较丑,但是还是可以一试. Python内置图形界面库--Tkinter ...

最新文章

  1. 100万人同时抢1万张火车票,极限并发带来的思考
  2. 运行程序报“应用程序配置不正确”或者缺少运行库造成程序不可移植的问题...
  3. xml中1字节的UTF-8序列的字节1无效([字符编码]Invalid byte 1 of 1-byte UTF-8 sequence终极解决方案)
  4. [C#] C#访问数据库(SQL Server版本)
  5. 低功耗蓝牙系统结构流程图
  6. 排序算法 —— 插入排序
  7. 学习笔记 | 传统企业互联网改革之道
  8. es 安装kopf_Elasticsearch-kopf导览
  9. android之uniapp原生打包
  10. reactive streams与观察者模式
  11. 我与分布式机器学习的故事
  12. 31个实用find命令的案例
  13. PostgreSQL命令导入sql文件
  14. C# Microsoft.Office.Interop.Word 将多个word合成一个并插入图片 转换成pdf
  15. IEC60068-2-5太阳辐射模拟试验测试
  16. Canto加速市场的发展,连接全球的金融衍生品市场
  17. 远不止三色,图片被压扁了,用Python的Tkinter做一个既高颜值又好用的计算器
  18. 简述变分法在泛函极值问题中的应用
  19. 挑战大数据 金仓助力三农自助支付系统
  20. Python实现门禁管理系统(源码)

热门文章

  1. 显示控件——图标类之动画图标
  2. php discuz 顶,discuz模拟登录实现自动顶帖php程序 - Discuz
  3. (分享)基于JQuery的WEB套打设计器jatoolsPrinter2.0
  4. PlantUML教程及主题模板
  5. 计算机乐谱制作师专业,国家职业标准:计算机乐谱制作师
  6. 基于VS2019 C++的跨平台(Linux)开发(2.1)——网络基础
  7. 罗斯蒙特3051变送器以后的发展方向
  8. python的PDF工具
  9. 相机中的透视投影几何——讨论相机中的正交投影,弱透视投影以及透视的一些性质
  10. 五、MySQL主从复制原理