在大型计算时(例如神经网络训练),经常会遇到计算时间过长,无法知道当前的计算进度,无法判断程序是否进入死循环等问题。采用进度条可以在一定程度上了解当前进度,判断后续所需的计算时间,缓解等待过程中的焦虑。在计算进程中可以放心地去干其他事情。
1.常规进度条
常规的方法是使用打印字符的形式展示当前的进度,但这一类进度条很容易被淹没在其他需要显示的内容中,使用起来比较繁琐,想要知道多个任务进行到了第几个了还要从打印历史中手动查看。字符进度显示的例子如下:
计算进度:|>>>>>>>>>>>>>>>>>>>>>>>>>>>>>| 100.0%
2.GUI进度条
最为合适的方法是创建弹窗进度条,在弹窗中实时显示当前进行的子任务及子任务执行的进度。
基于pycharm+pyqt5 实现的弹窗进度条的效果如下,pyqt5 的安装请搜索其他案例,在此不具体介绍了。
填坑:请注意在pyqt5图形设计界面中生成的python代码直接运行时会出现卡顿,直到进度条到达100%后才显示的情况。这是因为没有设置窗口刷新,请在设置好进度值之后使用 QApplication.processEvents() 进行窗口的刷新,简单易行,对于间隔时间较短的情况无需建立两个线程进行通信。
不想了解上述坑的同学,可以直接使用如下简单易用的代码,将其放在自己的用户包中,随时调用(喜欢和认为有用朋友可以给我点个赞!):
# _*_coding: UTF-8_*_
# 开发作者 :TXH
# 开发时间 :2020-09-08 10:20
# 文件名称 :Qt_Processbar.py
# 开发工具 :Python 3.7 + Pycharm IDE
from PyQt5.QtWidgets import QApplication, QWidget, QDialog, QLabel, QLineEdit, QProgressBar, \
QPushButton, QVBoxLayout, QHBoxLayout, QGridLayout, QDialogButtonBox
from PyQt5.QtCore import Qt, QBasicTimer, QThread,QRect
import sys
class ProgressBar(QDialog):
def __init__(self, parent=None):
super(ProgressBar, self).__init__(parent)
# Qdialog窗体的设置
self.resize(500, 32) # QDialog窗的大小
# 创建并设置 QProcessbar
self.progressBar = QProgressBar(self) # 创建
self.progressBar.setMinimum(0) #设置进度条最小值
self.progressBar.setMaximum(100) # 设置进度条最大值
self.progressBar.setValue(0) # 进度条初始值为0
self.progressBar.setGeometry(QRect(1, 3, 499, 28)) # 设置进度条在 QDialog 中的位置 [左,上,右,下]
self.show()
def setValue(self,task_number,total_task_number, value): # 设置总任务进度和子任务进度
if task_number=='0' and total_task_number=='0':
self.setWindowTitle(self.tr('正在处理中'))
else:
label = "正在处理:" + "第" + str(task_number) + "/" + str(total_task_number)+'个任务'
self.setWindowTitle(self.tr(label)) # 顶部的标题
self.progressBar.setValue(value)
class pyqtbar():
'''
task_number和 total_task_number都为 0 时,不显示当前进行的任务情况
task_number<total_task_number 都为整数,错误的设置将出现错误显示,暂未设置报错警告
# 使用示例
import time
bar = pyqtbar() # 创建实例
total_number=10
# 任务1
task_id=1
for process in range(1, 100):
time.sleep(0.05)
bar.set_value(task_id,total_number,process) # 刷新进度条
# 任务2
task_id = 2
for process in range(1, 100):
time.sleep(0.05)
bar.set_value(task_id, total_number,process)
bar.close # 关闭 bar 和 app
'''
def __init__(self):
self.app = QApplication(sys.argv) # 打开系统 app
self.progressbar = ProgressBar() # 初始化 ProcessBar实例
def set_value(self,task_number,total_task_number,i):
self.progressbar.setValue(str(task_number), str(total_task_number),i + 1) # 更新进度条的值
QApplication.processEvents() # 实时刷新显示
@property
def close(self):
self.progressbar.close() # 关闭进度条
self.app.exit() # 关闭系统 app
if __name__ == '__main__':
import time
# 使用示例
bar=pyqtbar() # 创建实例
total_number=10 # 总任务数
# 任务1
task_id=1 # 子任务序号
for process in range(1, 100):
time.sleep(0.05)
bar.set_value(task_id,total_number,process) # 刷新进度条
# 任务2
task_id = 2
for process in range(1, 100):
time.sleep(0.05)
bar.set_value(task_id, total_number,process)
bar.close # 关闭 bar 和 app
3.多线程进度条方案(用于单步耗时较高的情况)
对于时长超过5秒的高耗时迭代计算,第2节的方案会出现卡顿,在此给出使用多线程进行计算的方案。其中需要:1.将要运行的代码块构建成 Qthread 类(子线程),在Qthread实例中不断将单个任务进度和总的任务进度通过pyqtSignal功能发送给主线程;2.pyqtbar()类在初始化时需要传入上述Qthread实例。后续主线程根据子线程发送的信号进行更新,不会出现卡顿的情况。
踩坑:在Qthread 代码块中不要使用基于pyqt5 的matplotlib 绘图功能,否则整个程序将卡在绘图代码处。
# _*_coding: UTF-8_*_
# 开发作者 :TXH
# 开发时间 :2020/9/14 1:44
# 文件名称 :pyqtbar.py
# 开发工具 :Python 3.7+ Pycharm IDE
from PyQt5 import QtWidgets, QtCore
import sys
from PyQt5.QtCore import *
import time
class ProcessBar(QtWidgets.QWidget):
def __init__(self,work):
super().__init__()
self.work=work
self.run_work()
def run_work(self):
# 创建线程
# 连接信号
self.work._signal.connect(self.call_backlog) # 进程连接回传到GUI的事件
# 开始线程
self.work.start()
# 进度条设置
self.pbar = QtWidgets.QProgressBar(self)
self.pbar.setMinimum(0) # 设置进度条最小值
self.pbar.setMaximum(100) # 设置进度条最大值
self.pbar.setValue(0) # 进度条初始值为0
self.pbar.setGeometry(QRect(1, 3, 499, 28)) # 设置进度条在 QDialog 中的位置 [左,上,右,下]
# 窗口初始化
self.setGeometry(300, 300, 500, 32)
self.setWindowTitle('正在处理中')
self.show()
# self.work = None # 初始化线程
def call_backlog(self, msg,task_number,total_task_number):
if task_number==0 and total_task_number==0:
self.setWindowTitle(self.tr('正在处理中'))
else:
label = "正在处理:" + "第" + str(task_number) + "/" + str(total_task_number)+'个任务'
self.setWindowTitle(self.tr(label)) # 顶部的标题
self.pbar.setValue(int(msg)) # 将线程的参数传入进度条
class pyqtbar():
def __init__(self,work):
self.app = QtWidgets.QApplication(sys.argv)
self.myshow = ProcessBar(work)
self.myshow.show()
sys.exit(self.app.exec_())
if __name__ == "__main__":
# 继承QThread
class Runthread(QThread):
# 通过类成员对象定义信号对象
_signal = pyqtSignal(int,int,int)
def __init__(self):
super(Runthread, self).__init__()
def run(self):
task_number = 0
total_task_number = 9
for i in range(100):
##########################
# #
#将需要计算的代码块放在此处#
# #
##########################
time.sleep(0.1)
self._signal.emit(i + 1,task_number ,total_task_number ) # 发送实时任务进度和总任务进度
work = Runthread()
bar=pyqtbar(work)