34

It seems that the standard way of creating a figure in matplotlib doesn't behave as I'd expect in python: by default calling fig = matplotlib.figure() in a loop will hold on to all the figures created, and eventually run out of memory.

There are quite a few posts which deal with workarounds, but requiring explicit calls to matplotlib.pyplot.close(fig) seems a bit hackish. What I'd like is a simple way to make fig reference counted, so I won't have to worry about memory leaks. Is there some way to do this?

Community
  • 1
  • 1
Shep
  • 7,990
  • 8
  • 49
  • 71
  • 1
    It's really more like manual memory management, in this case, the figure is an external resource (like a file descriptor) to the Windowing system, and `plt.figure()` is the constructor, while `plt.close(fig)` is the destructor. Although there are many levels of destruction due to `clf` and `cla` and others. In this case, the proper way to do this would be to use the `with` bracketing idiom ("context manager"). – CMCDragonkai Nov 05 '17 at 07:13

3 Answers3

38

If you create the figure without using plt.figure, then it should be reference counted as you expect. For example (This is using the non-interactive Agg backend, as well.)

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)
Joe Kington
  • 275,208
  • 71
  • 604
  • 463
  • 3
    Without the `FigureCanvas(fig)` I get an exception when I try to save the figure. I take it that a `Figure` must always been drawn with a `FigureCanvas`? – Shep May 02 '13 at 13:53
  • 3
    Yep! Otherwise nothing can be drawn (the artists are created, but drawing doesn't happen until you save/show the plot). It's a wart in the API; ideally the canvas would be initiated along with the figure. It might make more sense if you use `canvas.print_figure(filename)` instead of `fig.savefig(filename)` (which is actually what `fig.savefig` does behind the scenes). That's purely for your own understanding, though (the canvas is the backend-specific part that handles drawing/saving). The end result is the same. – Joe Kington May 02 '13 at 15:08
  • 1
    Hmm, so does the canvas have to exist at all before saving? I'm wondering if `FigureCanvas(fig).print_figure(filename)` would work as a one-line print function. – Shep May 10 '13 at 07:43
  • That's a good question! I'm not 100% certain it will work everywhere. However, as long as you don't need to draw the plot before you save it (some things have to be drawn to determine their extents), you should be fine. I don't think any of the basic plotting functions should care whether or not the canvas has been initialized yet. It should at least work for almost anything, at any rate. – Joe Kington May 10 '13 at 11:29
  • 8
    It's a bit insane that Matplotlib _still_ assumes use of the non-garbage collecting `pylab` figure manager for... well, **everything.** While the above approach suffices for the narrow case of a single static backend known at development time, it's unclear how this scales up to the general case of an arbitrary dynamic backend selected at interpretation time. _Aaaany–way._ Matplotlib. Just "Ugh!," sometimes. – Cecil Curry Jan 04 '16 at 03:09
  • @CecilCurry - You can easily bypass the pyplot state machine for general interactive figures as well. It just takes a three lines to get the equivalent of `plt.show()` instead of one. – Joe Kington Jan 04 '16 at 13:54
  • 1
    @JoeKington I don't quite follow, I'm afraid. The above solution assumes use of a single preselected backend – which is great! Don't get me wrong. I'm delighted to have even that. But I'd _really_ love to have a generic solution dynamically applicable to whatever backend may happen to be currently enabled at runtime. While the `matplotlib.get_backend()` function does get the name of the currently enabled backend (if any), a generic solution requires the actual in-memory module object of that backend. Matplotlib doesn't appear to have a corresponding getter for that. – Cecil Curry Jan 17 '16 at 06:48
  • 1
    @CecilCurry - The example above is only for non-interactive backends. In that case, you really do want `Agg` every time. For interactive backends, you need to do a bit more legwork (see http://stackoverflow.com/questions/28631741/prevent-matplotlib-statefulness/28633419#28633419 ) You can always dynamically import the appropriate backend through the usual `__import__` statements. However, if you're using an interactive backend, there's really no reason to avoid using `plt.figure` and `plt.show`. What's your use-case? You may be going against-the-grain when you don't need to. – Joe Kington Jan 17 '16 at 15:39
  • We're _definitely_ going against the grain. Unfortunately, use of the `pyplot` API appears to be prohibitively expensive for long-lived applications opening multiple figure windows: e.g., `matplotlib/pyplot.py:424: RuntimeWarning: More than 20 figures have been opened. Figures created through the pyplot interface ('matplotlib.pyplot.figure') are retained until explicitly closed and may consume too much memory. (To control this warning, see the rcParam 'figure.max_open_warning').` – Cecil Curry Jan 18 '16 at 01:00
  • 1
    In our case, figure windows are opened in a non-blocking manner and hence _cannot_ by definition by explicitly closed. Ergo, the `pyplot` API is a non-starter for us. We also need to dynamically leverage different backends depending on platform (e.g., `MacOSX` on OS X, `TkAgg` on Linux, and Odin knows what on Windows). But thanks for the hasty and helpful replies, Joe! You're awesomeness is legion. We're going to give dynamic backend importation the ol' college try, as you suggest. – Cecil Curry Jan 18 '16 at 01:06
  • Also, your synopsis at [prevent-matplotlib-statefulness](http://stackoverflow.com/questions/28631741/prevent-matplotlib-statefulness/28633419#28633419) is _phenomenal_. No, really! My upvotes are the least I can give. I'd already reverse-engineered most of what you describe, but it's great to have third-hand confirmation that I'm not fundamentally insane. It really _is_ that hard to integrate Matplotlib into larger-scale applications. It's just not designed for that sort of thing. – Cecil Curry Jan 18 '16 at 02:09
  • In this case does it mean we cannot interactively show the figure using something like `plt.show` ? – yuqli Feb 05 '19 at 15:51
  • Could someone please answer this? **1)** no error even if we don't use `canvas = FigureCanvas(fig)`. Why is it? **2)** *"The pylab figure manager will be bypassed in this instance. This means that `fig` will be garbage collected as you'd expect."* ->So, which figure manager is being used in this code? Which part of the code is exactly the "bypassing" part? – starriet Dec 15 '21 at 12:35
  • P.S. for anyone like me: From the Matplotlib docs, *"If you are creating many figures, make sure you explicitly call pyplot.close on the figures you are not using, because this will enable pyplot to properly clean up the memory."* – starriet Dec 15 '21 at 12:36
10

If you're only going to be saving figures rather than displaying them, you can use:

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

This is arguably no less hacky, but whatever.

1''
  • 26,823
  • 32
  • 143
  • 200
  • Thanks, but while this tells me how to save a figure and close it it doesn't answer the question—it's a bit like answering "how do I use smart pointers in C++" with "allocate a raw pointer and then call `delete` when you're done with it". – Shep Jun 03 '16 at 19:13
  • That's fair, just suggesting an alternate solution to "How can I not worry about memory leaks" that still uses the standard `plt` API. – 1'' Jun 03 '16 at 19:47
2

If you want to profit from the use of pyplot and have the possibility to wrap the figure in a class of your own, you can use the __del__ method of your class to close the figure.

Something like:

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)

Whenever the garbage collector decides to delete your figure because it is inaccessible, the matplotlib figure will be closed.

However note that if someone has grabbed the figure (but not your wrapper) they might be annoyed that you closed it. It probably can be solved with some more thought or imposing some restrictions on usage.

Dharman
  • 30,962
  • 25
  • 85
  • 135
Pol Febrer
  • 121
  • 2