一、问题描述
最近几天,我在用python开发一个数据处理的小工具。需要将xls文件中的大量数据(少则几千行多则几万行)读取出来后进行处理。其中一个功能是需要实现将读取到的原始数据和计算出来的结果在软件界面中以表格形式展示出来。
在python应用开发中,我基本用wxFormBuilder进行软件界面的设计,因此用到wxPython的时候很多。wxPython中的表格类是wx.grid。这次开发过程中遇到的问题是,通过pandas将几万行数据从xls文件中读取到dataframe中后,再更新到软件主界面中的wxGrid中时,由于数据很大,导致软件ui在一段时间内无响应,一直到数据全部写入"时点库存表“中后,才会响应。
(从xls文件读取几万行数据后,向wxGrid加载数据时,出现未响应提示)
本软件中将数据从dataframe对象(self.df_kcb)写入到ui上显示为“时点库存表“的wxGrid对象(self.m_grid_kcb)中的关键代码如下:
# self.m_grid_kcb是wxGrid对象,self.df_kcb是dataframe对象
# 在执行下述代码之前,会首先清空self.m_grid_kcb
# 然后根据self.df_kcb中的数据行数修改self.m_grid_kcb行数,使两者保持一致
for x in range(self.grid_kcb_rows): # self.grid_kcb_rows为表格行数
for y in range(6): # 本软件中为时点库存表为6列
self.m_grid_kcb.SetCellValue(x, y, str(self.df_kcb.iloc[x][y])) # 逐单元格将数据写入wxGrid中
该代码使用了双循环语句,逐个单元格的写入数据,效率不高,在遇到几万行数据时,就导致了ui无响应了。
二、解决办法
我在网上搜索相应的解决办法,找到了如下的一段话。
————————————————————————————————————————
在Python中,将大量数据写入wxPython的wx.grid.Grid控件可能会导致UI响应变慢。为了提高响应性,可以尝试以下方法:
1.分批写入数据:不要一次性将所有数据都加载到wx.grid.Grid中,而是使用分页或滚动机制,每次只加载可见的数据。
2.使用wx.lib.delayedresult:这个模块可以帮助你在一个单独的线程中处理数据的加载,从而不会阻塞UI线程。
3.避免不必要的更新:只有当数据真正改变时才更新wx.grid.Grid,避免不必要的重绘和布局操作。
————————————————————————————————————————
第一个方法,我在去年开发一款软件的时候已经用过了,那款软件中的dataframe对象内的数据量也是有上万行,采用分页显示,每次只显示当前页的几十行数据,通过导航按钮切换页面,没有出现过未响应的情况。但这次我不准备采用这个方法,因为走重复的路是无法学到新知识的。
第二个方法,在那段话后面确实附带了相关的代码示例,但是我尝试将示例融合到我的代码中,确没有成功,总是出现错误。
第三个方法嘛,则不适合当前的应用场景。因为我的应用每次都会选择加载不同的数据,数据量也不一样,必然会对wxGrid中的所有内容都进行更新。
我仍然坚持在一个页面内显示所有的数据,那么就必须解决ui无响应的问题。再次搜索的时候,我使用了“python 如何实现在后台线程中将大量数据写入wxGrid”的关键字起到了作用,百度AI给了我一个有效的答案。
这个答案中的示例直接进行测试是有效的,在想wxGrid加载数据过程中,ui界面不会出现未响应的情况。
(直接运行后,界面有响应,往下拖可以看到数据还没有更新完)
(拖动滑动条的过程中,可以看到数据在更新)
这个代码的关键是用threading模块创建后台线程,并在线程中使用wxCallAfter方法更新wxGrid。
为了实现后台线程操作,要创建一个类:
class WorkerThread(threading.Thread):
""" 后台线程向wxGrid加载数据 """
def __init__(self, grid, df, rows, cols):
threading.Thread.__init__(self)
self.grid = grid
self.df = df
self.rows = rows
self.cols = cols
def run(self):
for row in range(self.rows):
for col in range(self.cols):
wx.CallAfter(self.grid.SetCellValue, row, col, str(self.df.iloc[row][col]))
然后在我的主程序中,将之前的更新wxGrid的双循环语句替换成以下的代码:
self.worker = WorkerThread(self.m_grid_kcb, self.df_kcb, self.grid_kcb_rows, 6) # 后台线程更新到grid
self.worker.start()
我参照示例修改了自己的软件,让更新wxGrid的操作在后台线程中执行,就解决了ui未响应的问题了。效果如下:
(没有出现未响应的情况,可以拖动滑条到底,表格的行数已更新,但数据还没有更新完)
(数据更新完的情况,在更新过程中,可以拖动滑条查看数据更新情况)
三、百度AI给的答案和代码
我在百度ai搜索到的文字内容和示例代码如下(代码中我添加了一点标注):
在Python中,你可以使用threading模块来创建后台线程,并在该线程中更新wxPython界面中的wxGrid。为了安全地在不同的线程之间更新GUI,你需要使用wxPython提供的wx.CallAfter方法。
以下是一个简单的例子,展示如何在后台线程中将大量数据写入wxGrid:
import wx
import wx.grid
import threading
import time
class MyFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent, -1, "wxGrid Example")
self.grid = wx.grid.Grid(self)
# 创建并设置表格
self.grid.CreateGrid(100, 2) # 100行2列
self.grid.SetRowLabelSize(-1)
self.grid.SetColLabelSize(-1)
# 创建并启动后台线程(这两行代码代替我之前写的双循环语句)
self.worker = WorkerThread(self.grid)
self.worker.start()
# 这个类可以直接复制到自己的代码中后根据情况进行修改
class WorkerThread(threading.Thread):
def __init__(self, grid):
threading.Thread.__init__(self)
self.grid = grid
def run(self):
# 模拟数据生成(这行代码,实际应用要替换成实际要写入wxGrid中的数据)
data = [(i, i * 2) for i in range(100)]
# 将数据写入wxGrid表格中(range方法中的行数和列数,按实际应用修改)
for row in range(len(data)):
for col in range(2):
wx.CallAfter(self.grid.SetCellValue, row, col, str(data[row][col]))
# 模拟耗时操作(这行代码,在实际应用中要删除)
time.sleep(0.1)
def main():
app = wx.App(False)
frame = MyFrame(None)
frame.Show(True)
app.MainLoop()
if __name__ == '__main__':
main()
在这个例子中,WorkerThread类是一个后台线程,它负责生成和处理数据。在MyFrame类的初始化方法中,创建了一个wx.grid.Grid对象,并启动了WorkerThread。
在WorkerThread的run方法中,我们通过双层循环模拟了将数据填充到wxGrid中的过程。每设置完一个单元格的值后,我们使用wx.CallAfter来确保在GUI线程中执行这个更新。这样可以避免在后台线程中直接更新GUI,从而避免潜在的线程安全问题。
请注意,这个例子中的time.sleep(0.1)是模拟耗时操作,实际应用中应该去掉或替换为实际的计算或IO操作。此外,根据实际情况,你可能需要更复杂的同步机制,例如使用事件或其他同步原语来通知主线程更新GUI。
希望以上的内容,对大家有用。