警告有太多未结数字


166

在使用创建大量图形的脚本中fix, ax = plt.subplots(...),我收到警告RuntimeWarning:已打开20个以上图形。通过pyplot接口(matplotlib.pyplot.figure)创建的图形将保留到显式关闭,并且可能会占用过多内存。

但是,我不明白为什么会收到此警告,因为使用保存了该数字之后fig.savefig(...),我使用删除了该警告fig.clear(); del fig。在我的代码中,我一次都没有打开多个图形。不过,我仍然收到有关太多未结数字的警告。这是什么意思/如何避免收到警告?


9
如果您正在执行大量操作,并且没有交互显示任何内容,则最好plt完全绕开。例如stackoverflow.com/a/16337909/325565(不要插入我自己的答案之一,但这是我能最快找到的答案...)
Joe Kington 2014年

1
@JoeKington谢谢您,这是一个更好的解决方案
hihell

Joe Kington的答案应该在主要答案列表中。它起作用并且还解决了plt.close()减慢Don Kirby提到的程序的问题。
NatalieL

Answers:


198

在图形对象上使用.clf.cla,而不要创建一个图形。来自@DavidZwicker

假设您已导入pyplot

import matplotlib.pyplot as plt

plt.cla()清除轴,即当前图形中的当前活动轴。保持其他轴不变。

plt.clf()清除所有轴的整个当前图形,但使窗口保持打开状态,以便可以将其重新用于其他图形。

plt.close()关闭一个window,如果没有另外指定,它将是当前窗口。plt.close('all')将关闭所有未结数字。

之所以del fig不起作用,是因为pyplot状态机一直在参考周围的数字(因为要知道“当前数字”是什么,就必须这样做)。这意味着即使您删除对该图引用,也至少有一个活动引用,因此将永远不会进行垃圾回收。

由于我在这里针对此答案轮询集体智慧,因此@JoeKington在评论中提到plt.close(fig)将从pylab状态机(plt._pylab_helpers.Gcf)中删除特定的图形实例,并允许对其进行垃圾回收。


1
嗯 还有clffigure类,但没有close。为什么实际上不del fig关闭并删除该图?
andreas-h 2014年

2
@ andreas-h我的猜测:对于诸如窗口管理器之类的复杂事物,它拥有自己的处理程序,可能需要的清理工作比将其超出范围要多。您close无法在图形对象上使用的权利,请将其称为plt.close(),而不是fig.clf()
上钩

5
@ andreas-h-基本上del fig不起作用的原因是,在这种特殊情况下,给它一个__del__方法(基本上会调用plt.close(fig))将导致循环引用,而fig拥有一个__del__方法将导致其他事情不被垃圾收集。(或者无论如何,这都是我含糊的回忆。)无论如何,这肯定有点烦人,但是您应该致电plt.close(fig)而不是del fig。附带一提,matplotlib可以为此使用上下文管理器...
Joe Kington

6
@Hooked-为了更清楚一点,您可以编辑问题以提及plt.close(fig)将从pylab状态机(plt._pylab_helpers.Gcf)中删除特定的图形实例并允许对其进行垃圾回收的问题。
乔·肯顿2014年

2
@JoeKington plt有点混乱,并且有关于如何重新做一堆的想法。上下文管理器很吸引人。 github.com/matplotlib/matplotlib/pull/2736github.com/matplotlib/matplotlib/pull/2624
tacaswell

32

这是Hooked的答案的更多细节。当我第一次阅读该答案时,我错过了致电说明,clf() 而不是创建一个新图形clf()如果您自己去创建另一个图形,它本身并没有帮助。

这是一个引起警告的简单示例:

from matplotlib import pyplot as plt, patches
import os


def main():
    path = 'figures'
    for i in range(21):
        _fig, ax = plt.subplots()
        x = range(3*i)
        y = [n*n for n in x]
        ax.add_patch(patches.Rectangle(xy=(i, 1), width=i, height=10))
        plt.step(x, y, linewidth=2, where='mid')
        figname = 'fig_{}.png'.format(i)
        dest = os.path.join(path, figname)
        plt.savefig(dest)  # write image to file
        plt.clf()
    print('Done.')

main()

为了避免警告,我必须将调用拉到subplots()循环之外。为了继续看到矩形,我需要切换clf()cla()。这将清除轴,而不会移除轴本身。

from matplotlib import pyplot as plt, patches
import os


def main():
    path = 'figures'
    _fig, ax = plt.subplots()
    for i in range(21):
        x = range(3*i)
        y = [n*n for n in x]
        ax.add_patch(patches.Rectangle(xy=(i, 1), width=i, height=10))
        plt.step(x, y, linewidth=2, where='mid')
        figname = 'fig_{}.png'.format(i)
        dest = os.path.join(path, figname)
        plt.savefig(dest)  # write image to file
        plt.cla()
    print('Done.')

main()

如果要批量生成图,则可能必须同时使用cla()close()。我遇到了一个问题,即一个批次可以拥有20多个地块而没有抱怨,但在20批次之后它会抱怨。我通过cla()在每个地块之后和close()每个批次之后使用来解决该问题。

from matplotlib import pyplot as plt, patches
import os


def main():
    for i in range(21):
        print('Batch {}'.format(i))
        make_plots('figures')
    print('Done.')


def make_plots(path):
    fig, ax = plt.subplots()
    for i in range(21):
        x = range(3 * i)
        y = [n * n for n in x]
        ax.add_patch(patches.Rectangle(xy=(i, 1), width=i, height=10))
        plt.step(x, y, linewidth=2, where='mid')
        figname = 'fig_{}.png'.format(i)
        dest = os.path.join(path, figname)
        plt.savefig(dest)  # write image to file
        plt.cla()
    plt.close(fig)


main()

我测量了性能以查看是否值得在一批中重复使用该图,并且当我close()在每次绘图后都调用时,这个小示例程序从41s减至49s(慢20%)。


这是一个很好的答案。接受的答案并不能真正解决手头上的实际问题,即内存消耗。
凯尔(Kyle)

24

如果您打算有意识地在内存中保留许多绘图,但又不想被警告,则可以在生成图形之前更新选项。

import matplotlib.pyplot as plt
plt.rcParams.update({'figure.max_open_warning': 0})

这将防止发出警告,而无需更改有关内存管理方式的任何内容。


在Jupyter环境中,只要显示该图的单元存在,内存分配是否会持续?
matanster,

2
@matanster,我将其发布为自己的问题。我开始回答,然后意识到我对jupyter的内核管理真的不了解,无法诚实地回答。
强大的

@matanster在用户显式关闭内核之前,所有分配给它们的变量和内存都存在。它没有链接到单元格。在较新的Jupyter Hub中,系统可以关闭内核(可以配置)。
greatvovan

0

以下代码段为我解决了这个问题:


class FigureWrapper(object):
    '''Frees underlying figure when it goes out of scope. 
    '''

    def __init__(self, figure):
        self._figure = figure

    def __del__(self):
        plt.close(self._figure)
        print("Figure removed")


# .....
    f, ax = plt.subplots(1, figsize=(20, 20))
    _wrapped_figure = FigureWrapper(f)

    ax.plot(...
    plt.savefig(...
# .....

_wrapped_figure超出范围时,运行时将在内部调用我们的__del__()方法plt.close()。即使在_wrapped_figure构造函数之后触发异常,也会发生这种情况。


0

如果您只想暂时取消警告,这也很有用:

    import matplotlib.pyplot as plt
       
    with plt.rc_context(rc={'figure.max_open_warning': 0}):
        lots_of_plots()
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.