在我使用fix, ax = plt.subplots(…)创建许多图形的脚本中,我得到警告RuntimeWarning:超过20个图形已被打开。通过pyplot接口(matplotlib.pyplot.figure)创建的图形将被保留,直到显式关闭,并且可能会消耗太多内存。

然而,我不明白为什么我会得到这个警告,因为在用fig.savefig(…)保存图形后,我用fig.clear()删除它;在我的代码中,我没有一次打开一个以上的数字。尽管如此,我还是得到了关于太多未公开数字的警告。这是什么意思/我怎样才能避免得到警告?


当前回答

如果您有意在内存中保留许多图,但不想收到警告,则可以在生成图形之前更新选项。

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

这将防止在不改变内存管理方式的情况下发出警告。

其他回答

在您的图形对象上使用.clf或.cla,而不是创建一个新的图形。从@DavidZwicker

假设您已经导入pyplot为

import matplotlib.pyplot as plt

Plt.cla()清除一个轴,即当前图中的当前活动轴。它不影响其他轴。

Plt.clf()清除整个当前图形及其所有轴,但保持窗口打开,以便其他图形可以重用它。

Plt.close()关闭一个窗口,如果没有指定的话,该窗口将是当前窗口。Plt.close ('all')将关闭所有打开的数字。

del fig不起作用的原因是pyplot状态机保留了对周围图形的引用(如果它想知道“当前图形”是什么,就必须这样做)。这意味着即使您删除了对图的引用,也至少有一个活动的引用,因此它永远不会被垃圾收集。

由于我在这里对这个答案进行了集体智慧的投票,@JoeKington在评论中提到,plt.close(图)将从pylab状态机(plt._pylab_helpers.Gcf)中删除一个特定的图形实例,并允许它被垃圾收集。

如果您有意在内存中保留许多图,但不想收到警告,则可以在生成图形之前更新选项。

import matplotlib.pyplot as plt
plt.rcParams.update({'figure.max_open_warning': 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构造函数之后触发异常,也会发生这种情况。

这里有更多的细节来扩展hook的答案。当我第一次阅读这个答案时,我错过了调用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()

我测量了性能,看看是否值得在批处理中重用图形,当我在每个plot之后调用close()时,这个小示例程序的速度从41秒减慢到49秒(慢了20%)。

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

如果使用这个,就不会得到错误,这是最简单的方法。