警告过多的开放数字

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/…

以上是关于警告过多的开放数字的主要内容,如果未能解决你的问题,请参考以下文章

NodeJS 警告:可能的事件发射器泄漏。添加了 11 个开放式侦听器

聚焦美联储对“央行数字货币”态度趋开放

开发者大会会前活动,“开放日”线上惊喜来袭!

浅析开放平台应用系统性能调优

单一职责原则开放-封闭原则依赖倒转原则

华为首次采用数字人全程实时手语直播,并宣布全面开放手语服务能力