Matplotlib和Pyplot.close()不释放内存?-与后端相关的Qt4Agg

39

编辑:如果我将matplotlib的后端从'Qt4Agg'显式更改为只是'Agg',那么我就能够无错误地运行我的代码。我认为这是后端中的一个错误吗?

我正在编写一些用于自动处理相当大量数据的代码。首先,该代码解析我的数据文件并存储所有相关位。然后,我有不同的函数来生成我需要的每个图形(总共约25个)。但是,我一直遇到某种内存错误,我认为这是因为Matplotlib / PyPlot没有正确释放内存。

每个绘图函数都以pyplot.close(fig)命令结束,由于我只想保存图形而不是立即查看它们,因此它们不包括pyplot.show()。

如果我在解释器中逐个运行绘图函数,则不会遇到任何问题。但是,如果我创建一个单独的函数,该函数依次调用每个绘图函数,那么我会遇到“MemoryError:Could not allocate memory for path”。

有人遇到过这样的问题吗?似乎与在循环中绘制时Matplotlib内存不足有关,但pyplot.close()并不能解决我的问题。
这是我代码中典型的绘图函数:
def TypicalPlot(self, title=None, comment=False, save=False, show=True):

    if title is None:
        title = self.dat.title

    fig = plt.figure()
    host = SubplotHost(fig, 111)
    fig.add_subplot(host)
    par = host.twinx()
    host.set_xlabel("Time (hrs)")
    host.set_ylabel("Power (W)")
    par.set_ylabel("Temperature (C)")
    p1, = host.plot(self.dat.timebase1, self.dat.pwr, 'b,', label="Power",
                    markevery= self.skip)
    p2, = par.plot(self.dat.timebase2, self.dat.Temp1, 'r,', 
                   label="Temp 1", markevery= self.skip)
    p3, = par.plot(self.dat.timebase2, self.dat.Temp2, 'g,', 
                   label="Temp 2", markevery= self.skip)
    p4, = par.plot(self.dat.timebase2, self.dat.Temp3, 'm,', 
                   label="Temp 3", markevery= self.skip)
    host.axis["left"].label.set_color(p1.get_color())
    # par.axis["right"].label.set_color(p2.get_color())
    #host.legend(loc='lower left')
    plt.title(title+" Temperature")

    leg=host.legend(loc='lower left',fancybox=True)
    #leg.get_frame().set_alpha(0.5)
    frame  = leg.get_frame()
    frame.set_facecolor('0.80')

    ### make the legend text smaller
    for t in leg.get_texts():
        t.set_fontsize('small')

    ### set the legend text color to the same color as the plots for added
    ### readability
    leg.get_texts()[0].set_color(p1.get_color())
    leg.get_texts()[1].set_color(p2.get_color())
    leg.get_texts()[2].set_color(p3.get_color())    
    leg.get_texts()[3].set_color(p4.get_color())        

    if show is True and save is True:
        plt.show()
        plt.savefig('temp.png')
    elif show is True and save is False:
        plt.show()
    elif show is False and save is True:
        plt.savefig('temp.png')
        plt.clf()
        plt.close(fig)

如果我现在在终端中运行
MyClass.TypicalPlot(save=True, show = False) 

然后我就不会收到任何错误信息。对于我所有的绘图函数也是如此。 如果我创建一个新的函数,它执行以下操作:
def saveAllPlots(self, comments = False):

        if self.comment is None: comment = False
        else: comment = True
        self.TypicalPlot(save=True, show=False, comment=comment)
        self.AnotherPlot(save=True, show=False)
        self.AnotherPlot2(save=True, show=False)
        self.AnotherPlot3(save=True, show=False)
        ...etc, etc, etc

然后它运行了大约一半的图表,然后我收到了“MemoryError: Could not allocate memory for path”的错误提示。


3
在函数末尾,无论是在if/elif之后还是独立于其之后,请尝试添加“del fig”语句。 - Roland Smith
更改matplotlib后端可以解决问题。我已经更新了我的原始问题以反映这一点,但我会感激任何进一步的信息。 - FakeDIY
2
我不明白为什么会发生这种情况,但是在修复它方面,可以尝试在开头使用import gc进行显式垃圾回收,并在每次循环迭代后使用gc.collect() - Ferdinand van Wyk
我曾经遇到过保存多个图形的类似问题。 在我的情况下,Matplotlib从以下最后一个图形绘制了数据。 我使用plt = None来解决它。 我猜在你的情况下,在TypiclaPlot被调用时,在代码末尾使用fig = None可能有助于创建一个全新的图形。 - BigZ
4
我的方法是将最后两条评论合并起来。先使用 fig = None,然后再显式地调用 gc.collect() 进行垃圾回收。 - RobertB
显示剩余2条评论
2个回答

1
我曾经遇到过一个非常类似的问题。我认为Matplotlib在内部为每个图形保留了引用。考虑以下代码,创建三个单独的图形:
import matplotlib.pyplot as plt
import numpy as np

# block 1
f, ax = plt.subplots(1)
plt.plot(np.arange(10), np.random.random(10))
plt.title("first")
print 'first', sys.getrefcount(f), sys.getrefcount(ax)

# bock 2
f, ax = plt.subplots(1)
plt.plot(np.arange(10), np.random.random(10)+1)
plt.title("second")
print 'second', sys.getrefcount(f), sys.getrefcount(ax)

# block 3
f, ax = plt.subplots(1)
plt.plot(np.arange(10), np.random.random(10)+2)
plt.title("third")
print 'third', sys.getrefcount(f), sys.getrefcount(ax)

plt.show()

print 'after show', sys.getrefcount(f), sys.getrefcount(ax)

输出:

first 69 26
second 69 26
third 69 26
after show 147 39

这很反直觉,因为我们多次重新定义了fax。每个块都创建了一个新的图形,可以通过plt引用。创建另一个图形会更改plt可访问的最顶层引用。但是必须有一些内部引用,允许plt.show()显示所有图形。这些引用似乎是持久的,因此图形不会被gc收集。
我采用的解决方法是改变绘图的数据。事后看来,这是一个更好的方法:
plt.ion()
f, ax = plt.subplots(1)
line = ax.plot(np.arange(10), np.random.random(10))[0]
plt.title('first')
plt.show()

for i, s in [(2, 'second'), (3, 'third')]:
    x = np.arange(10)
    y = np.random.random(10)+i
    line.set_data(x, y)
    ax.set_xlim(np.min(x), np.max(x))
    ax.set_ylim(np.min(y), np.max(y))
    plt.title(s)
    plt.draw()
    raw_input(s)

唯一的缺点是你必须保持窗口打开以查看图形。而没有 raw_input,程序将会直接运行。


1
我认为它这样做的原因是,当它遍历所有不同的图表时,由于没有正确释放内存,所以最终会耗尽内存。
为什么不试着创建大约3个程序,每个程序都做几个图表,而不是一个程序做所有的图表:
程序1:图表1-8
程序2:图表9-16
程序3:图表17-25
希望这可以帮到你@FakeDIY :)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接