警告有太多未结数字

问题:警告有太多未结数字

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

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

In a script where I create many figures with fix, ax = plt.subplots(...), I get the warning RuntimeWarning: More than 20 figures have been opened. Figures created through the pyplot interface (matplotlib.pyplot.figure) are retained until explicitly closed and may consume too much memory.

However, I don’t understand why I get this warning, because after saving the figure with fig.savefig(...), I delete it with fig.clear(); del fig. At no point in my code, I have more than one figure open at a time. Still, I get the warning about too many open figures. What does that mean / how can I avoid getting the warning?


回答 0

在图形对象上使用.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)中删除特定的图形实例,并允许对其进行垃圾回收。

Use .clf or .cla on your figure object instead of creating a new figure. From @DavidZwicker

Assuming you have imported pyplot as

import matplotlib.pyplot as plt

plt.cla() clears an axis, i.e. the currently active axis in the current figure. It leaves the other axes untouched.

plt.clf() clears the entire current figure with all its axes, but leaves the window opened, such that it may be reused for other plots.

plt.close() closes a window, which will be the current window, if not specified otherwise. plt.close('all') will close all open figures.

The reason that del fig does not work is that the pyplot state-machine keeps a reference to the figure around (as it must if it is going to know what the ‘current figure’ is). This means that even if you delete your ref to the figure, there is at least one live ref, hence it will never be garbage collected.

Since I’m polling on the collective wisdom here for this answer, @JoeKington mentions in the comments that plt.close(fig) will remove a specific figure instance from the pylab state machine (plt._pylab_helpers.Gcf) and allow it to be garbage collected.


回答 1

这是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%)。

Here’s a bit more detail to expand on Hooked’s answer. When I first read that answer, I missed the instruction to call clf() instead of creating a new figure. clf() on its own doesn’t help if you then go and create another figure.

Here’s a trivial example that causes the warning:

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()

To avoid the warning, I have to pull the call to subplots() outside the loop. In order to keep seeing the rectangles, I need to switch clf() to cla(). That clears the axis without removing the axis itself.

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()

If you’re generating plots in batches, you might have to use both cla() and close(). I ran into a problem where a batch could have more than 20 plots without complaining, but it would complain after 20 batches. I fixed that by using cla() after each plot, and close() after each batch.

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()

I measured the performance to see if it was worth reusing the figure within a batch, and this little sample program slowed from 41s to 49s (20% slower) when I just called close() after every plot.


回答 2

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

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

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

If you intend to knowingly keep many plots in memory, but don’t want to be warned about it, you can update your options prior to generating figures.

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

This will prevent the warning from being emitted without changing anything about the way memory is managed.


回答 3

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


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构造函数之后触发异常,也会发生这种情况。

The following snippet solved the issue for me:


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(...
# .....

When _wrapped_figure goes out of scope the runtime calls our __del__() method with plt.close() inside. It happens even if exception fires after _wrapped_figure constructor.


回答 4

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

    import matplotlib.pyplot as plt
       
    with plt.rc_context(rc={'figure.max_open_warning': 0}):
        lots_of_plots()

This is also useful if you only want to temporarily suppress the warning:

import matplotlib.pyplot as plt
       
with plt.rc_context(rc={'figure.max_open_warning': 0}):
    lots_of_plots()