警告过多的开放数字

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

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

182660 次浏览

在图形对象上使用.clf.cla,而不是创建图形。从@DavidZwicker

假设你已经导入pyplot

import matplotlib.pyplot as plt

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

plt.clf()清除整个当前数字和它所有的轴,但让窗口打开,这样它可以被重用为其他地块。

plt.close()关闭一个窗口,如果没有另外指定,它将是当前窗口。plt.close('all')将关闭所有打开的图形。

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

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

这里有更多关于上的回答的细节。当我第一次读这个答案时,我错过了调用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个批次后抱怨。我通过在每个plot之后使用cla(),在每个batch之后使用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})

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

下面的代码片段为我解决了这个问题:


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超出作用域时,运行时调用包含plt.close()__del__()方法。即使异常在_wrapped_figure构造函数之后触发也会发生。

如果你只是想暂时抑制警告,这也很有用:

import matplotlib.pyplot as plt
       

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

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

默认情况下,Matplotlib保存通过pyplot创建的所有图形的引用。如果用于存储matplotlib图形的单个变量(例如“fig"”)被修改和重写,而没有清除图形,则所有的图形都保留在RAM内存中。使用plt.cla()plt.clf()而不是修改和重用fig变量是很重要的。如果您正在绘制数千个不同的图,并保存它们而不清除图形,那么最终您的RAM将耗尽,程序将终止。如果绘制许多图形,清除坐标轴和图形将对内存使用产生重大影响。您可以在任务管理器(Windows)或系统监视器(Linux)中监视RAM消耗。首先您的RAM将耗尽,然后操作系统开始消耗SWAP内存。一旦两者都耗尽,程序将自动终止。最好清除图形和坐标轴,如果不需要就关闭它们。