performance - Python GUI (tkinter.ttk) 应用程序速度慢

标签 performance python-3.x tkinter slowdown ttk

我已经用 ttk 完成了(工作)应用程序。它使用自行创建的模块来显示与兼容性相关的控件和在其上绘制一些图形的 Canvas 。当我创建对象的实例时,它会启动一个线程,在其中处理串行输入并将其附加到列表中(每个图形一个列表)。当我有 3-6 个图表时,应用程序会明显变慢。它也有一些错误,但当我完成一般概念后我会解决它们。

可能对你有帮助的事情:

  • comport 是一个自写对象的实例,派生自 LabelFrame 和 Serial.Serial
  • 图形的坐标存储在列表字典中: self.graphs = {} self.graphs['name1']=[] 存储的坐标数 取决于 Canvas 的宽度,因此每张图大约有 1000-2000 个。有六个 图表 - 请乘以 6
  • 随着每个新坐标的到达,我从列表中弹出(0)并 append() 新坐标
  • 我忘了,我还存储了每组新坐标到达的时间 在单独的列表中
  • 我使用 preiodic 调用函数来处理列表: self.after(100, func=self.periodicCall)因此每 100 毫秒我从 Canvas 中删除(全部) 我用一组线条绘制每个图表。所以如果我有 1000 个坐标 在 6 格中,我画了 6000 条小线
  • 当然还有一些服务信息,例如一些标尺

所以我想这个想法很清楚。我想找出更好的方法。我刚刚开始接触Python和编程,所以我想请你原谅我要发布的代码以及它会给你带来的痛苦。我没有任何编程风格,我想修复它。至少有一点。因此,欢迎对您在代码中看到的任何内容提出任何其他评论。

#-------------------------------------------------------------------------------
# Name:        dataVisualizer
# Purpose:
#
# Author:      dccharacter
#
# Created:     23.03.2012
# Copyright:   (c) dccharacter 2012
# Licence:     <your licence>
#-------------------------------------------------------------------------------
#!/usr/bin/env python

from tkinter import *
from tkinter.ttk import *
from robowidgets.serialPortGui import *
import threading
import re
import atexit
import random
from datetime import datetime
import time

class dataVisualizer(LabelFrame):
    def __init__(self, master, comport , cnf={}, **kw):
        self.master = master
        self.comport = comport
        LabelFrame.__init__(self, *cnf, **kw)

        self.messageVar = StringVar()
        Label(self, text="Message format regexp:").pack()
        self.messagePattern = Entry(self, width = 20, text = 234234, textvariable = self.messageVar);
        self.messageVar.set(r'(-*\d+),(-*\d+),(-*\d+),(-*\d+),(-*\d+),(-*\d+)')
        self.messagePattern.pack()
        Button(self, text = "Pause", command = self.pause).pack()
        self.pauseFlag = TRUE

        self.canvWidth, self.canvHeight = 1000, 700
        self.density = 1 ##width of pixel - the bigger, the wider graph
        self.numOfDots = self.canvWidth//self.density
        self.graphs = {}
        self.graphs['name1']=[]
        self.graphs['name2']=[]
        self.graphs['name3']=[]
        self.graphs['name4']=[]
        self.graphs['name5']=[]
        self.graphs['name6']=[]
        self.timings = []
        self.zeroTiming = datetime.now()
        self.colors = ['red', 'blue', 'green', 'orange', 'violet', 'black', 'cyan']

        self.canv = Canvas(self, width = self.canvWidth, height = self.canvHeight)
        self.canv.pack()

        self.thread = threading.Thread(target = self.workerThread)
        self.thread.start()

        self.serialData = []

        self.periodicCall()

    def pause(self):
        self.pauseFlag = ~self.pauseFlag

    def redraw(self):
        self.canv.delete(ALL)

        colorIndex = 0
        for graphName in self.graphs:
            runningAverage = sum(self.graphs[graphName][-10:])//10
            text = str(runningAverage)
            self.canv.create_text(self.canvWidth-60, 20*(colorIndex+1), text = text,
                fill = self.colors[colorIndex], anchor = W)
            prev_xxx, prev_yyy = 0, 0
            for yyy in self.graphs[graphName]:
                self.canv.create_line(prev_xxx, prev_yyy, prev_xxx+self.density, self.canvHeight//2 - yyy,
                    width = 1.4, fill = self.colors[colorIndex])
                prev_xxx, prev_yyy = prev_xxx+self.density, self.canvHeight//2 - yyy
            colorIndex = colorIndex + 1
        self.drawMesh()

    def drawMesh(self):
        self.canv.create_rectangle(3, 3, self.canvWidth,
            self.canvHeight, outline = 'black', width = 2)
        self.canv.create_line(0, self.canvHeight/2, self.canvWidth,
            self.canvHeight/2, fill="black", width = 1)

        mouseX = self.canv.winfo_pointerx() - self.canv.winfo_rootx()
        mouseY = self.canv.winfo_pointery() - self.canv.winfo_rooty()

        if mouseY < 60: aaa = -1
        else: aaa = 1
        if mouseX > self.canvWidth - 200 : bbb = -12
        else: bbb = 1
        try:
            self.canv.create_rectangle(mouseX + 10*bbb - 5, mouseY - 20*aaa +10,
                mouseX + 10*bbb + 115, mouseY - 20*aaa - 30, outline = "black",
                fill = "red")
            self.canv.create_text(mouseX + 10*bbb, mouseY - 40*aaa,
                text = "t="+str(self.timings[mouseX//self.density]),
                anchor = W)
            self.canv.create_text(mouseX + 10*bbb, mouseY - 20*aaa,
                text = "value="+str(self.canvHeight//2 - mouseY),
                anchor = W)
        except IndexError:
            pass
        self.canv.create_line(mouseX, 0, mouseX,
            self.canvHeight, fill="blue", dash = [4, 1, 2, 1], width = 1)
        self.canv.create_line(0, mouseY, self.canvWidth,
            mouseY, fill="blue", dash = [4, 1, 2, 1], width = 1)


    def periodicCall(self):
        self.redraw()
        self.after(100, func=self.periodicCall)

    def workerThread(self):

        while (1):
            try:
                if self.comport.isOpen() and (self.pauseFlag == TRUE):
                    comLine = self.comport.readline()
                    if len(self.timings) == self.numOfDots:
                        self.timings.pop(0)
                    td = datetime.now() - self.zeroTiming

                    ##  b'271;-3:-50\r\n'
                    parsedLine = re.search(self.messagePattern.get(), str(comLine))
                    index = 1
                    if parsedLine:
                        self.timings.append(td)
                        for graphName in self.graphs:
                            if len(self.graphs[graphName]) == self.numOfDots:
                                self.graphs[graphName].pop(0)
                            try:
                                self.graphs[graphName].append(int(parsedLine.group(index)))
                            except IndexError:
                                self.graphs[graphName].append(0)
                            index = index + 1
                else:
                    self.comport.flush();
                    time.sleep(1)
            except TclError:
                self.thread._stop()

def main():
    root = Tk()
    mainWindow = Frame(root)
    mainWindow.pack()
    port = comPortWidget(mainWindow)
    port.pack()
    dv = dataVisualizer(mainWindow, port)
    dv.pack()
    root.mainloop()

if __name__ == '__main__':
    main()

串行部分 - 也可能会滞后(当我过去每隔一秒左右重新枚举端口时就会滞后......)

#-------------------------------------------------------------------------------
# Name:        robowidgets
# Purpose:
#
# Author:      dccharacter
#
# Created:     10.03.2012
# Copyright:   (c) dccharacter 2012
# Licence:     <your licence>
#-------------------------------------------------------------------------------
#!/usr/bin/env python

import serial
from serial.tools.list_ports_windows import comports
from tkinter import *
from tkinter.ttk import *

class comPortWidget(LabelFrame, serial.Serial):

    commonComPortSpeeds = ["1200", "2400", "4800", "9600", "14400", "19200", "38400", "57600", "115200"]

    def __init__(self, master=None, cnf={}, **kw):
        """Construct a comPortWidget widget with the parent MASTER.

        STANDARD OPTIONS

            borderwidth, cursor, font, foreground,
            highlightbackground, highlightcolor,
            highlightthickness, padx, pady, relief,
            takefocus, text, background, class, colormap, container,
            height, labelanchor, labelwidget,
            visual, width

        WIDGET-SPECIFIC OPTIONS


        """
        self.master = master
        LabelFrame.__init__(self, master, text="Serial settings", *cnf, **kw)
        serial.Serial.__init__(self)
        self.parent = master
        self.draw()

    def draw(self):
        self.strVarComPort = StringVar()
        self.comboComport = Combobox(self,
            textvariable=self.strVarComPort)

        self.comboComport.grid(row=0, column=1)
        self.labelComportName = Label(self, text="Com port:")
        self.labelComportName.grid(row=0, column=0)

        self.strVarComSpeed = StringVar()
        self.comboComSpeed = Combobox(self,
            textvariable=self.strVarComSpeed, values=self.commonComPortSpeeds)
        self.comboComSpeed.current(len(self.commonComPortSpeeds)-1)
        self.comboComSpeed.grid(row=1, column=1)
        self.labelComSpeed = Label(self, text="Com speed:")
        self.labelComSpeed.grid(row=1, column=0)

        self.buttonComOpen = Button(self, text="Open port", command=self.openPort)
        self.buttonComOpen.grid(row=0, column=2)
        self.buttonComClose = Button(self, text="Close port", command=self.closePort)
        self.buttonComClose.grid(row=1, column=2)
        self.buttonRefreshPorts = Button(self, text="Re", width=3, command=self.refreshComPortsCombo)
        ##self.buttonRefreshPorts.grid(row=0, column=2)

        self.refreshComPortsCombo()

    def refreshComPortsCombo(self):
        listComs = self.enumerateComPorts()
        if not listComs:
            listComs.append("No com ports found")
            self.disableControls(~self.isOpen())
            self.buttonComClose.configure(state=DISABLED)
        else:
            self.disableControls(self.isOpen())
        self.buttonRefreshPorts.configure(state=NORMAL)
        self.comboComport.config(values=listComs)
        self.comboComport.current(len(listComs)-1)
        ##self.after(500, func=self.refreshComPortsCombo)

    def enumerateComPorts(self):
        """
        Returns the list ofcom port names in the system or an empty list if
        no ports found
        """
        listComs = []
        for port, desc, hwid in sorted(comports()):
            listComs.append(port)
        return listComs

    def openPort(self):
        if self.isOpen():
            return
        self.port = self.comboComport.get()
        self.baudrate = int(self.comboComSpeed.get())
        self.timeout = 1
        try:
            self.open()
            self.disableControls(self.isOpen())
        except IOError:
            pass

    def closePort(self):
        if self.isOpen():
            self.flush()
            self.close()
            self.disableControls(self.isOpen())

    def disableControls(self, isConnected):
        if isConnected:
            self.labelComportName.configure(state=DISABLED)
            self.labelComSpeed.configure(state=DISABLED)
            self.comboComport.configure(state=DISABLED)
            self.comboComSpeed.configure(state=DISABLED)
            self.buttonComClose.configure(state=NORMAL)
            self.buttonComOpen.configure(state=DISABLED)
            self.buttonRefreshPorts.configure(state=DISABLED)
        else:
            self.labelComportName.configure(state=NORMAL)
            self.labelComSpeed.configure(state=NORMAL)
            self.comboComport.configure(state=NORMAL)
            self.comboComSpeed.configure(state=NORMAL)
            self.buttonComClose.configure(state=DISABLED)
            self.buttonComOpen.configure(state=NORMAL)
            self.buttonRefreshPorts.configure(state=NORMAL)

def main():
    pass

if __name__ == '__main__':
    main()

更新:我按照布莱恩的建议做了。现在我有两个屏幕重绘功能。它们之间的区别在于,首先将所有线移动到左侧,在右侧添加新线并删除那些从 Canvas 上掉下来的线。第二个将线条向左移动,并将从 Canvas 上掉落的元素重新部署到右侧(不创建新元素)。相对于我的初始变体,这些都有巨大的改进,但我肉眼看不出两者之间有很大的区别 - 也许如果我有更多的元素,我会看到。不过,后者更适合我的应用程序,因为我不必跟踪那些从悬崖上掉下来的人。

这里的功能:

def drawGraph(self): ###needed for self.updateGraph2() only as it is creates the lines
    for graphNum in range(0, self.numOfGraphs):
        self.graphLines.append([])
        self.graphData.append([0,]*self.numOfDots)
        for iii in range(0,self.numOfDots):
            self.graphLines[graphNum].append(
                self.canv.create_line(0,0,0,0,fill=self.colors[graphNum],
                width=1.2, tags=('graphLines', 'graph'+str(graphNum)))
                )


def updateGraph2(self):
    while not self.queue.empty():
        iTuple = self.queue.get()
        self.canv.move('graphLines', -self.density,0)
        for graphNum in range(0, self.numOfGraphs):
            try: self.graphData[graphNum].append(iTuple[graphNum])
            except IndexError:
                self.graphData[graphNum].append(0)
            self.graphData[graphNum].pop(0)
            self.graphLines[graphNum].append(self.graphLines[graphNum].pop(0))
            self.canv.coords(self.graphLines[graphNum][-1],
                self.canv.winfo_width()-self.density,
                int(int(self.graphData[graphNum][-2])+int(self.canv.winfo_height()//2)),
                self.canv.winfo_width(),
                int(int(self.graphData[graphNum][-1])+int(self.canv.winfo_height()//2))
                )

def updateGraph(self):
    while not self.queue.empty():
        self.timingIndex = self.timingIndex + 1
        self.canv.move('graphLines', -self.density, 0)
        iTuple = self.queue.get()
        for iii in range(0, len(iTuple)):
            yyy = int(iTuple[iii])+self.canv.winfo_height()//2
            if yyy < 0: yyy = 0
            if yyy > self.canv.winfo_height(): yyy = self.canv.winfo_height()
            prev_yyy = int(self.prevTuple[iii])+self.canv.winfo_height()//2
            if prev_yyy < 0: prev_yyy = 0
            if prev_yyy > self.canv.winfo_height(): prev_yyy = self.canv.winfo_height()
            self.canv.create_line(
                self.canv.winfo_width()-self.density, prev_yyy,
                self.canv.winfo_width(), yyy,
                width = 1.4, fill = self.colors[iii], tags=('graphLines','graph'+str(iii)))
        self.prevTuple = iTuple

        self.canv.addtag_overlapping('todelete',-1,-1,-3,self.canv.winfo_height()+1)
        self.canv.dtag('preserve','todelete')
        self.canv.delete('todelete')

最佳答案

我对 Canvas 的理解是,分配的元素 ID 越多,速度就越慢。它可以毫无问题地处理数万个(甚至可能是数十万个),但如果您每 100 毫秒创建和删除 6000 个项目,这可能就是您的问题。即使您正在删除这些项目,它仍然会影响性能,尤其是当您每秒创建 60,000 个项目时。

不必每 100 毫秒删除所有项目,只需将项目移出屏幕并记住它们,然后通过使用 coords 方法更改它们在新图表中的坐标来重用它们。

关于performance - Python GUI (tkinter.ttk) 应用程序速度慢,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9855314/

相关文章:

Python return 语句不会中断 for 循环

python - 将成员变量添加到 python 列表对象

python - tkinter 按钮没有对齐

Python 3 - Tkinter 按钮命令

java - 在 full GC 之前获得预先警告

matlab - 如果不在数组中,则提高附加数组元素的速度

python - 在日志记录配置中使用socket.SOCK_STREAM时为"Protocol wrong type for socket"

python-3.x - 更新标签上的文本

java - 字节格式的 longs 的快速(呃)比较?

Asp.net 保证响应时间