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?

1
  • 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"). Commented Nov 5, 2017 at 7:13

3 Answers 3

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)
Sign up to request clarification or add additional context in comments.

14 Comments

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?
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.
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.
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.
@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.
|
11

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.

2 Comments

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".
That's fair, just suggesting an alternate solution to "How can I not worry about memory leaks" that still uses the standard plt API.
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.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.