关于太多公开数字的警告

and*_*s-h 128 python matplotlib python-3.x

在我创建许多数字的脚本中fix, ax = plt.subplots(...),我收到警告RuntimeWarning:已打开超过20个数字.通过pyplot接口(matplotlib.pyplot.figure)创建的数字将保留,直到明确关闭,并可能消耗太多内存.

但是,我不明白为什么我得到这个警告,因为保存图后fig.savefig(...),我删除它fig.clear(); del fig.我的代码中没有任何一点,我一次打开多个数字.尽管如此,我还是得到了关于太多公开数字的警告.这是什么意思/如何避免收到警告?

Hoo*_*ked 160

使用.clf.cla在图形对象上而不是创建图形.来自@DavidZwicker

假设您已导入pyplot

import matplotlib.pyplot as plt
Run Code Online (Sandbox Code Playgroud)

plt.cla()清除轴,即当前图中当前活动的轴.它使其他轴保持不变.

plt.clf()用它的所有轴清除整个当前图形,但是打开窗口,这样它就可以重新用于其他图形.

plt.close()如果没有另外指定,则关闭一个窗口,该窗口将是当前窗口.plt.close('all')将关闭所有未结数字.

del fig不起作用的原因是pyplot状态机保持对图形的引用(如果它将知道"当前图形"是什么必须的话).这意味着即使您删除了您的参考图,也至少有一个实时参考,因此它永远不会被垃圾收集.

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

  • @andreas-h - 基本上,`del fig`不起作用的原因是给它一个`__del__`方法(基本上称为'plt.close(fig)`)最终会导致循环引用case,和`fig`有一个`__del__`方法会导致其他东西不被垃圾收集.(或者那是我模糊的回忆.无论如何.)无论如何,它肯定有点烦人,但你应该叫`plt.close(fig)`而不是'del fig`.另外,matplotlib可以真正使用上下文管理器... (4认同)
  • @Hooked - 为了使它更清楚,你可以编辑你的问题,提到`plt.close(fig)`将从pylab状态机(`plt._pylab_helpers.Gcf`)中删除一个特定的数字实例并允许它被垃圾收集. (4认同)
  • 嗯。`figure` 类有 `clf`,但没有 `close`。为什么`del fig`实际上不关闭并删除图形? (2认同)
  • @andreas -h我的猜测:对于像窗口管理器一样复杂的东西,它有自己的处理程序,可能需要更多的清理工作,而不是把东西放在范围之外.你关于`close`的权利不会对图形对象起作用,称之为`plt.close()`,而不是`fig.clf()`. (2认同)
  • @JoeKington`plt`有点乱,有想法如何重新做一堆.上下文管理器很有趣....请参阅https://github.com/matplotlib/matplotlib/pull/2736,https://github.com/matplotlib/matplotlib/pull/2624 (2认同)

Don*_*kby 23

这里有更多细节来扩展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()
Run Code Online (Sandbox Code Playgroud)

为了避免警告,我必须将呼叫拉到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()
Run Code Online (Sandbox Code Playgroud)

如果您要批量生成绘图,则可能必须同时使用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()
Run Code Online (Sandbox Code Playgroud)

我测量了性能以确定是否值得重复使用批量中的数字,并且当我close()在每个绘图之后调用时,这个小样本程序从41s减慢到49s(慢20%).

  • 这是一个很好的答案。接受的答案并没有真正解决手头的实际问题,即内存消耗。 (2认同)

mig*_*ile 13

如果您打算故意在内存中保留许多图表,但不希望对其进行警告,则可以在生成数据之前更新选项.

import matplotlib.pyplot as plt
plt.rcParams.update({'figure.max_open_warning': 0})
Run Code Online (Sandbox Code Playgroud)

这样可以防止在不改变内存管理方式的情况下发出警告.

  • @matanster,我会发布它自己的问题.我开始回答,然后意识到我真的不太了解jupyter对内核的管理来诚实地回答. (2认同)