创建一个引用计数的图形。

34

看起来,在Python中,使用matplotlib创建图形的标准方式不像我期望的那样运行顺畅:默认情况下,在循环中调用fig = matplotlib.figure()将保持所有创建的图形,并最终耗尽内存。

有一些文章介绍解决方案,但需要显式调用matplotlib.pyplot.close(fig),这似乎有些hackish。我想要的是一种简单的方法,使fig具有引用计数功能,这样我就不必担心内存泄漏了。是否有某种方法可以做到这一点?


1
这更像是手动内存管理,在这种情况下,该图形是窗口系统的外部资源(如文件描述符),而plt.figure()是构造函数,而plt.close(fig)是析构函数。虽然由于clfcla和其他因素存在许多级别的销毁。在这种情况下,正确的方法是使用“with”括号惯用语(“上下文管理器”)。 - CMCDragonkai
3个回答

38

如果您创建图形而不使用plt.figure,则它应该像您期望的那样被引用计数。例如(这是使用非交互式Agg后端的情况)。

from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
from matplotlib.figure import Figure

# The pylab figure manager will be bypassed in this instance.
# This means that `fig` will be garbage collected as you'd expect.
fig = Figure()
canvas = FigureCanvas(fig)
ax = fig.add_subplot(111)

3
如果没有 FigureCanvas(fig),在尝试保存图形时会引发异常。我理解必须始终使用 FigureCanvas 绘制一个 Figure 吗? - Shep
3
是的!否则无法绘制任何内容(艺术家已经创建,但在保存/显示图形之前不会进行绘制)。这是API中的一个缺陷;理想情况下,画布应该与图形一起初始化。如果您使用canvas.print_figure(filename)而不是fig.savefig(filename)可能更有意义(实际上,fig.savefig在幕后执行的就是这个操作)。然而,这仅供您自己理解(画布是处理绘制/保存的特定于后端的部分)。最终结果是相同的。 - Joe Kington
8
Matplotlib仍然假定使用不进行垃圾回收的“pylab”图形管理器来处理……几乎所有事情,这有点疯狂。虽然上述方法对于在开发时已知单个静态后端的狭窄情况足够,但不清楚它如何扩展到在解释时选择任意动态后端的一般情况。总之,Matplotlib有时真是让人感到“呃!” - Cecil Curry
1
@JoeKington,恐怕我没明白你的意思。以上解决方案假设只使用单个预选的后端——这很好!别误会,即使只有这一个也令我十分高兴。但我真的希望有一个通用的解决方案,能够动态适用于当前已启用的任何后端。虽然 matplotlib.get_backend() 函数可以获得当前启用的后端名称(如果有的话),但通用的解决方案需要该后端实际在内存中的模块对象。Matplotlib 似乎没有相应的获取器。 - Cecil Curry
1
在我们的情况下,图形窗口以非阻塞方式打开,因此根据定义,它们不能被显式关闭。因此,pyplot API 对我们来说是不可行的。我们还需要根据平台动态利用不同的后端(例如,在 OS X 上使用 MacOSX,在 Linux 上使用 TkAgg,在 Windows 上使用 Odin 等)。但感谢 Joe 的迅速和有帮助的回复!你的优秀之处是无与伦比的。正如你所建议的那样,我们将尝试进行动态后端导入。 - Cecil Curry
显示剩余9条评论

10

如果你只想保存图像而不显示它们,可以使用以下代码:

def savefig(*args, **kwargs):
    plt.savefig(*args, **kwargs)
    plt.close(plt.gcf())

这可能看起来并不可靠,但是无论如何。


谢谢,但是虽然这告诉我如何保存并关闭一个图像,但它并没有回答问题——这有点像用“分配一个原始指针,然后在使用完毕后调用delete”来回答“如何在C++中使用智能指针”的问题。 - Shep
这是公平的,只是建议一种替代解决方案,“如何不用担心内存泄漏”,仍然使用标准的 plt API。 - 1''

2
如果你想从使用 pyplot 获得收益,并且有可能将图形包装在自己的类中,可以使用你的类的 __del__ 方法来关闭图形。例如:
import matplotlib.pyplot as plt

class MyFigure:
  """This is my class that just wraps a matplotlib figure"""
  def __init__(self):
    # Get a new figure using pyplot
    self.figure = plt.figure()

  def __del__(self):
    # This object is getting deleted, close its figure
    plt.close(self.figure)

无论何时垃圾收集器决定删除您的图形,因为它是不可访问的,matplotlib图形将被关闭。但请注意,如果有人抓住了这个图形(但没有抓住您的包装器),他们可能会对您关闭它感到烦恼。这可能需要一些思考或对使用进行一些限制来解决。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接