问题:警告有太多未结数字
在使用创建大量图形的脚本中fix, ax = plt.subplots(...)
,我收到警告RuntimeWarning:已打开20个以上图形。通过pyplot接口(matplotlib.pyplot.figure
)创建的图形将保留到显式关闭,并且可能会占用过多内存。
但是,我不明白为什么会收到此警告,因为使用保存了该数字之后fig.savefig(...)
,我使用删除了该警告fig.clear(); del fig
。在我的代码中,我一次都没有打开多个图形。不过,我仍然收到有关太多未结数字的警告。这是什么意思/如何避免收到警告?
回答 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)中删除特定的图形实例,并允许对其进行垃圾回收。
回答 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%)。
回答 2
如果您打算有意识地在内存中保留许多绘图,但又不想被警告,则可以在生成图形之前更新选项。
import matplotlib.pyplot as plt
plt.rcParams.update({'figure.max_open_warning': 0})
这将防止发出警告,而无需更改有关内存管理方式的任何内容。
回答 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
构造函数之后触发异常,也会发生这种情况。
回答 4
如果您只想暂时取消警告,这也很有用:
import matplotlib.pyplot as plt
with plt.rc_context(rc={'figure.max_open_warning': 0}):
lots_of_plots()