【问题标题】:Memory used increases with tkinter notebook and matplotlibtkinter notebook 和 matplotlib 使用的内存增加
【发布时间】:2019-09-16 20:29:23
【问题描述】:

我有一个使用 tkinter 创建 GUI 的小程序。它包含一个加载 .csv 文件的按钮,创建一个带有与 csv 文件中的列一样多的选项卡的笔记本。然后,在每个活动选项卡上(至少这是我的意图)我都有一个从图形创建的图。

程序按预期运行,唯一的问题是切换Tabs时,每次点击Tabs时使用的内存都会增加。 使用 Windows 任务管理器监控内存使用情况。 加载 csv 文件后,当我选择不加载新文件时,我没有看到已用内存下降。 如果我不调用绘图功能,则仅创建选项卡时,不会出现内存问题。
我已经尝试使用 gc.collect() 手动调用垃圾收集器,但这并没有帮助。这是我的代码:

import matplotlib
matplotlib.use('TkAgg')
import pandas as pd
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
from matplotlib.figure import Figure
import matplotlib.pyplot as plt
import os
import sys
import tkinter as tk
from tkinter import messagebox as msg
from tkinter import ttk, filedialog
##import gc

class Graphs(tk.Tk):

    def __init__(self, *args, **kwargs):

        tk.Tk.__init__(self, *args, **kwargs)
        container = tk.Frame(self)
        container.pack(side='top', fill='both', expand=1)

        container.grid_rowconfigure(0, weight=1)
        container.grid_columnconfigure(0, weight=1)

        self.protocol('WM_DELETE_WINDOW', self._destroyWindow)

        self.frames = {}

        frame = StartPage(parent=container, controller=self)
        self.frames[StartPage] = frame

        frame.grid(row=0, column=0, sticky='nsew')
        self.show_frame(StartPage)

    def show_frame(self, cont):
        frame = self.frames[cont]
        frame.tkraise()

    def _destroyWindow(self):
        self.quit()    # stops mainloop
        self.destroy()


class StartPage(tk.Frame):

    def __init__(self, parent, controller):
        tk.Frame.__init__(self, parent)

        # initialize lists
        self.tabs_list = []
        self.hdrs = []
        self.figs_list = []
        self.ax_list = []
        self.canvas_list = []
        self.toolbars_list = []

        # initialize Data Frame
        self.df = pd.DataFrame()

        self.nb = None
        self.canvas = None
        self.toolbar = None

        # create LOAD button
        self.btn = tk.Button(self, text = 'Load file', command=self.load_csv)
        self.btn.pack()

    def load_csv(self):
        ''' 
            Reset Data Frame;
            Destroy notebook if exists;
            Load CSV file.
        '''

        # reset Data Frame
        self.df = pd.DataFrame()

        # destroy notebook if exists
        if self.nb:
            self.nb.pack_forget()
            self.nb.destroy()

        self.nb = None

##        gc.collect()

        # Select CSV file
        self.file_path = filedialog.askopenfilename()

        if not self.file_path:
            msg.showinfo('Select CSV file', "No file chosen.")
            return
        try:
            # read csv file (exemple.csv)
            self.df = pd.read_csv(self.file_path, header=0)
        except:
            msg.showinfo('Select CSV file', 'Not a csv file / corrupt file.')
            return

        print(self.df.head())
        print(self.df.shape)
        # get dimensions
        self.m, self.n = self.df.shape

        # build the abscissa x from first column
        self.x = self.df.iloc[:,0]

        # create the notebook
        self.nb = ttk.Notebook(self)

        # allow Tab navigation
        self.nb.enable_traversal()

        # add Tabs
        for k in range(1, self.n):
            hdr = self.df.columns[k]
            self.hdrs.append(hdr)

            tab = tk.Frame(self.nb, name=hdr.lower())
            self.nb.add(tab, text=hdr)

            self.tabs_list.append(tab)

        self.nb.pack(fill='both', expand=1)

        # virtual event after a new tab is selected
        self.nb.bind("<<NotebookTabChanged>>", self.plotTH)


    def plotTH(self, event):

        '''
            Plot each Column from Data Frame on its own Tab/Figure
        '''

        # get path of the selected Tab
        tab_path = event.widget.nametowidget(event.widget.select())

        # add selected Tab to the list of Tabs
        self.tabs_list.append(tab_path)

        # get the Tab index;
        # When there are no tabs, .select() returns an empty string,
        # but .index('current') throws an exception;
        # nb.select() returns the Tab NAME (string) of the current selection
        if self.nb.select():
            i = self.nb.index('current')
            # get the Tab text
            tab_text = self.nb.tab(i)['text']
        else:
            return

        # remove previous figures ... not sure...
        # the used memory as seen in Task Manager still increases
        if self.canvas_list:
            for cnv in self.canvas_list:

                cnv.figure.get_axes().clear()

                cnv.get_tk_widget().pack_forget()
                cnv.get_tk_widget().destroy()

                cnv._tkcanvas.pack_forget()
                cnv._tkcanvas.destroy()

                cnv = None

        if self.figs_list:

            for fig in self.figs_list:
                fig.delaxes(fig.gca())
                plt.cla()
                fig.clf()
                fig.clear()
                plt.close(fig)
            self.figs_list = []

        # remove toolbar           
        for widget in tab_path.winfo_children():
            widget.pack_forget()
            widget.destroy()

        self.nb.update()    #!!!!!!!!!!!!

########        gc.collect()

        # prepare plotting
        fig = Figure(figsize=(7, 5), dpi=100)
        ax = fig.add_subplot(111)

        ax.plot(self.x, self.df.iloc[:,i+1], 'b-', linewidth=1, label=tab_text)
        ax.set_xlabel('index')
        ax.set_title(self.hdrs[i], fontsize = 8)
        ax.legend(loc='best')
        ax.grid()

        # add to list of figures
        self.figs_list.append(fig)
        # add to list of axes
        self.ax_list.append(ax)

        canvas = FigureCanvasTkAgg(fig, master=tab_path)

        # add to list of canvases
        self.canvas_list.append(canvas)
##        self.canvas.draw()
        canvas.draw_idle()

        canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)

        toolbar = NavigationToolbar2Tk(canvas, tab_path)

        # add to list of toolbars
        self.toolbars_list.append(toolbar)
        toolbar.update()

        canvas._tkcanvas.pack(side=tk.TOP, fill=tk.BOTH, expand=1)

    def clearPlot(self):
        """ not used"""
        pass


app = Graphs()
app.title('CSV Plots')
app.geometry('800x600+400+150')
app.resizable(True, True)

app.mainloop();

我使用以下代码创建了一个有效的 csv 文件:

import numpy as np
import pandas as pd

np.random.seed(0)

df = pd.DataFrame(np.random.randn(50,4), columns=['ALPHA', 'BETA', 'GAMMA', 'DELTA'])

df.index.names = ['Rec']

df.index = df.index + 1

df.to_csv('example.csv', index=True)

print(df)

我很抱歉发了这么长的帖子。我真的不知道从这里出发,所以任何帮助将不胜感激。

【问题讨论】:

  • 您好您的脚本工作,除了笔记本不是创建的问题。你能控制吗?
  • tab = tk.Frame(self.nb, name=hdr.lower()),这个小部件在哪里打包?
  • @1966bc:您好,感谢您的反馈。如果我没有遗漏什么,按照一些关于 tkinter notebook 的文档,我创建了一个 tabControl(又名 notebook):self.nb = ttk.Notebook(self);打包由:self.nb.pack(fill='both', expand=1)。

标签: python-3.x matplotlib memory tkinter


【解决方案1】:

以下列表均未清除;它们持有对不能被垃圾回收的大对象的引用。

self.tabs_list = []
self.hdrs = []
self.figs_list = []
self.ax_list = []
self.canvas_list = []
self.toolbars_list = []

您可能应该创建一个对象来保存每个选项卡的数据,并在更改选项卡时创建destroy/purge/reassign to None

【讨论】:

    【解决方案2】:

    我更改了代码;我意识到我正在为每个 TabChanged 事件创建一个新图形(与所有艺术家一起)和一个新画布(FigureCanvasTkAgg)。相反,我在将 Tab 添加到笔记本时创建了一个图形及其画布;我只在 TabChanged 发生时才进行绘图,并且在执行新的 CSV 文件加载时注意关闭所有绘图并销毁以前的笔记本。销毁笔记本也会移除画布。代码如下:

    import matplotlib
    import matplotlib.style as mplstyle
    matplotlib.use('TkAgg')
    import pandas as pd
    from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
    from matplotlib.figure import Figure
    import matplotlib.pyplot as plt
    import os, sys, time
    import tkinter as tk
    from tkinter import messagebox as msg
    from tkinter import ttk, filedialog
    
    
    mplstyle.use('fast')
    
    class Graphs(tk.Tk):
        def __init__(self, *args, **kwargs):
            tk.Tk.__init__(self, *args, **kwargs)
            container = tk.Frame(self)
            container.pack(side='top', fill='both', expand=1)
    
            container.grid_rowconfigure(0, weight=1)
            container.grid_columnconfigure(0, weight=1)
    
            self.protocol('WM_DELETE_WINDOW', self._destroyWindow)
    
            self.frames = {}
    
            frame = StartPage(parent=container, controller=self)
            self.frames[StartPage] = frame
    
            frame.grid(row=0, column=0, sticky='nsew')
            self.show_frame(StartPage)
    
        def show_frame(self, cont):
            frame = self.frames[cont]
            frame.tkraise()
    
        def _destroyWindow(self):
            self.quit()    # stops mainloop
            self.destroy()
    
    
    class StartPage(tk.Frame):
        def __init__(self, parent, controller):
            tk.Frame.__init__(self, parent)
    
            # initialize Data Frame
            self.df = pd.DataFrame()
    
            self.nb = None
            self.fig = None
            self.canvas = None
            self.toolbar = None
    
            # create LOAD button
            self.btn = tk.Button(self, text = 'Load file', command=self.load_csv)
            self.btn.pack()
    
        def load_csv(self):
            ''' Close the plots;
                Reset Data Frame;
                Destroy notebook if exists;
                Load CSV file.
            '''
    
            # Setting interactive mode off
            if plt.isinteractive():
                plt.ioff()
    
            plt.close("all")
    
            # reset Data Frame
            self.df = pd.DataFrame()
    
            # initialize list
            self.hdrs = []
    
            try:
                # destroy notebook if exists
                self.nb.pack_forget()
                self.nb.destroy()
            except:
                pass
    
            self.nb = None
    
            # Select CSV file
            self.file_path = filedialog.askopenfilename()
    
            if not self.file_path:
                msg.showinfo('Select CSV file', "No file chosen.")
                return
            try:
                # read csv file (exemple.csv)
                self.df = pd.read_csv(self.file_path, header=0)
            except:
                msg.showinfo('Select CSV file', 'Not a csv file / corrupt file.')
                return
    
            # get dimensions
            self.m, self.n = self.df.shape
    
            # build the abscissa x from first column
            self.x = self.df.iloc[:,0]
    
            # create the notebook
            self.nb = ttk.Notebook(self)
    
            # allow Tab navigation
            self.nb.enable_traversal()
    
            # add Tabs
            for k in range(1, self.n):
                hdr = self.df.columns[k]
                self.hdrs.append(hdr)
    
                tab = tk.Frame(self.nb, name=hdr.lower())
                self.nb.add(tab, text=hdr)
    
                self.fig = plt.figure(num=hdr.lower(), clear=True, figsize=(7, 5), dpi=100)
                # self.ax = self.fig.add_subplot()
                self.canvas = FigureCanvasTkAgg(self.fig, master=tab)
                self.canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)
    
                toolbar = NavigationToolbar2Tk(self.canvas, tab)
                toolbar.update()
                self.canvas._tkcanvas.pack(side=tk.TOP, fill=tk.BOTH, expand=1)
    
            self.nb.pack(fill='both', expand=1)
    
            # virtual event after a new tab is selected
            self.nb.bind("<<NotebookTabChanged>>", self.plotTH)
    
        def plotTH(self, event):
            '''
                Plot each Column from Data Frame on its own Tab/Figure
            '''
    
            # Setting interactive mode on is essential: plt.ion()
            if not plt.isinteractive():
                plt.ion()
    
            # tab index
            i = self.nb.index('current')
    
            # tab text
            tab_text = self.nb.select().split('.')[-1]
    
            # set current figure
            cf = plt.figure(tab_text)
            plt.clf()        
    
            # plotting
            ax = plt.subplot(111)  
    
            ax.plot(self.x, self.df.iloc[:,i+1], 'b-', linewidth=1, label=tab_text)
            ax.set_xlabel('index')
            ax.set_title(self.hdrs[i], fontsize = 8)
            ax.legend(loc='best')
            ax.grid()
    
            cf.canvas.draw()
    
    app = Graphs()
    app.title('CSV Plots')
    app.geometry('800x600+400+150')
    app.resizable(True, True)
    
    app.mainloop()
    

    【讨论】:

      猜你喜欢
      • 2017-11-01
      • 2019-01-26
      • 1970-01-01
      • 2021-10-05
      • 2020-04-19
      • 1970-01-01
      • 1970-01-01
      • 2018-06-23
      • 1970-01-01
      相关资源
      最近更新 更多