警告过多的开放数字
Posted
技术标签:
【中文标题】警告过多的开放数字【英文标题】:warning about too many open figures 【发布时间】:2014-03-20 00:09:50 【问题描述】:在我使用fix, ax = plt.subplots(...)
创建许多图形的脚本中,我收到警告RuntimeWarning:已打开超过 20 个图形。通过 pyplot 接口 (matplotlib.pyplot.figure
) 创建的图形会一直保留到显式关闭,并且可能会消耗太多内存。
但是,我不明白为什么我收到此警告,因为在使用fig.savefig(...)
保存该图后,我使用fig.clear(); del fig
将其删除。在我的代码中,我一次打开的图形不止一个。尽管如此,我还是收到了关于开放数字过多的警告。这是什么意思/我怎样才能避免收到警告?
【问题讨论】:
如果你做了很多这样的事情,并且没有以交互方式显示任何东西,你最好完全绕过plt
。例如。 ***.com/a/16337909/325565(不要插入我自己的答案之一,但这是我能找到的最快的答案...)
@JoeKington 谢谢你这是一个更好的解决方案
Joe Kington 的答案应该在主答案列表中。它有效并且还解决了 plt.close() 减慢 Don Kirby 提到的程序的问题。
【参考方案1】:
在您的图形对象上使用.clf
或.cla
,而不是创建一个新 图形。来自@DavidZwicker
假设您已将pyplot
导入为
import matplotlib.pyplot as plt
plt.cla()
clears an axis,即当前图中当前活动的轴。它使其他轴保持不变。
plt.clf()
clears the entire current figure 及其所有坐标轴,但将窗口保持打开状态,以便可以将其重新用于其他绘图。
plt.close()
closes a window,如果没有另外指定,它将是当前窗口。 plt.close('all')
将关闭所有打开的人物。
del fig
不起作用的原因是pyplot
状态机保留了对该图形的引用(如果它要知道“当前图形”是什么就必须这样做)。这意味着即使您删除 your 对图的引用,也至少有一个活动引用,因此永远不会被垃圾回收。
由于我在这里就这个答案的集体智慧进行投票,@JoeKington 在 cmets 中提到 plt.close(fig)
将从 pylab 状态机 (plt._pylab_helpers.Gcf) 中删除特定的图形实例并允许对其进行垃圾收集.
【讨论】:
嗯。figure
类有 clf
,但没有 close
。为什么del fig
实际上并没有关闭并删除图?
@andreas-h 我的猜测:对于像窗口管理器这样具有自己的处理程序的复杂事物,可能需要进行更多清理,而不是超出范围。 close
对图形对象不起作用的权利,将其称为 plt.close()
,而不是 fig.clf()
。
@andreas-h - 基本上,del fig
不起作用的原因是给它一个__del__
方法(基本上会调用plt.close(fig)
)最终会导致循环引用这种特殊情况,以及 fig
具有 __del__
方法将导致其他事物不被垃圾收集。 (或者那是我模糊的回忆。)无论如何,这肯定有点烦人,但你应该打电话给plt.close(fig)
而不是del fig
。顺便说一句,matplotlib 真的可以为此使用上下文管理器......
@Hooked - 为了更清楚一点,您可以编辑您的问题以提及 plt.close(fig)
将从 pylab 状态机 (plt._pylab_helpers.Gcf
) 中删除特定图形实例并允许它被垃圾收集。
@JoeKington plt
有点乱,有人想如何重新做一堆。上下文管理器很有趣......见github.com/matplotlib/matplotlib/pull/2736,github.com/matplotlib/matplotlib/pull/2624【参考方案2】:
这里有更多细节可以扩展 Hooked's answer。当我第一次阅读该答案时,我错过了调用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()
时,这个小示例程序从 41 秒减慢到 49 秒(慢了 20%)。
【讨论】:
这是一个很好的答案。接受的答案并没有真正解决手头的实际问题,即内存消耗。 @Kyle:你为什么这么说?接受的答案是清除一个数字,这意味着不需要制作另一个消耗内存的数字。它显然解决了内存问题。 男孩,2019 年对我来说一定是糟糕的一年。 :)【参考方案3】:如果您有意在内存中保留许多绘图,但又不想被警告,您可以在生成数据之前更新您的选项。
import matplotlib.pyplot as plt
plt.rcParams.update('figure.max_open_warning': 0)
这将防止发出警告,而不会更改任何有关内存管理方式的内容。
【讨论】:
在 Jupyter 环境中,只要显示绘图的单元格存在,内存分配是否会持续存在? @matanster,我会把它作为自己的问题发布。我开始回答,然后意识到我对 jupyter 内核管理的了解还不够,无法诚实回答。 @matanster 所有为它们分配的变量和内存都存在,直到内核被用户明确关闭。它不链接到单元格。在较新的 Jupyter Hub 中,系统可以关闭内核(可以配置)。【参考方案4】:如果您只想暂时抑制警告,这也很有用:
import matplotlib.pyplot as plt
with plt.rc_context(rc='figure.max_open_warning': 0):
lots_of_plots()
【讨论】:
【参考方案5】:import matplotlib.pyplot as plt
plt.rcParams.update('figure.max_open_warning': 0)
如果你使用它,你就不会得到那个错误,这是最简单的方法。
【讨论】:
所以您的解决方案是完全忽略警告?了解警告告诉您的内容是否有益(在这种情况下,您有太多打开的数字)并解决它(在这种情况下,可能希望在完成写入后关闭一个数字)。此警告通常表明客户端意外打开了其他图形,因此收到警告对于 plt 来说是一件有用的事情。【参考方案6】:下面的 sn-p 为我解决了这个问题:
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
构造函数之后触发异常,它也会发生。
【讨论】:
我喜欢这个想法,但是,如果您使用 pytest,则不能依赖垃圾收集来运行。请参阅此处的讨论:github.com/pytest-dev/pytest/discussions/…以上是关于警告过多的开放数字的主要内容,如果未能解决你的问题,请参考以下文章