260

In a script where I create many figures with fix, ax = plt.subplots(...), I get the warning 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.

However, I don't understand why I get this warning, because after saving the figure with fig.savefig(...), I delete it with fig.clear(); del fig. At no point in my code, I have more than one figure open at a time. Still, I get the warning about too many open figures. What does that mean / how can I avoid getting the warning?

NelsonGon
  • 13,015
  • 7
  • 27
  • 57
andreas-h
  • 10,679
  • 18
  • 60
  • 78
  • 11
    If you're doing a lot of this, and not displaying anything interactively, you may be better off bypassing `plt` altogether. E.g. http://stackoverflow.com/a/16337909/325565 (Not to plug one of my own answers, but it's the one I could find quickest...) – Joe Kington Feb 19 '14 at 15:07
  • Joe Kington's answer should be in the main answer list. It works and also addresses the problem with plt.close() slowing down the program that Don Kirby mentioned. – NatalieL Apr 10 '20 at 19:59

7 Answers7

313

Use .clf or .cla on your figure object instead of creating a new figure. From @DavidZwicker

Assuming you have imported pyplot as

import matplotlib.pyplot as plt

plt.cla() clears an axis, i.e. the currently active axis in the current figure. It leaves the other axes untouched.

plt.clf() clears the entire current figure with all its axes, but leaves the window opened, such that it may be reused for other plots.

plt.close() closes a window, which will be the current window, if not specified otherwise. plt.close('all') will close all open figures.

The reason that del fig does not work is that the pyplot state-machine keeps a reference to the figure around (as it must if it is going to know what the 'current figure' is). This means that even if you delete your ref to the figure, there is at least one live ref, hence it will never be garbage collected.

Since I'm polling on the collective wisdom here for this answer, @JoeKington mentions in the comments that plt.close(fig) will remove a specific figure instance from the pylab state machine (plt._pylab_helpers.Gcf) and allow it to be garbage collected.

Community
  • 1
  • 1
Hooked
  • 84,485
  • 43
  • 192
  • 261
  • 4
    Mhh. There is `clf` for the `figure` class, but not `close`. Why doesn't `del fig` actually close and delete the figure? – andreas-h Feb 19 '14 at 15:07
  • 3
    @andreas-h My guess: for something complex like a window manager with it's own handlers, there might be more cleanup needed than putting something out of scope. Your right that `close` won't work on the figure object, call it like `plt.close()`, instead of `fig.clf()`. – Hooked Feb 19 '14 at 15:12
  • 8
    @andreas-h - Basically, the reason that `del fig` doesn't work is that giving it a `__del__` method (which would basically call `plt.close(fig)`) would wind up causing circular references in this particular case, and `fig` having a `__del__` method will cause other things to not be garbage collected. (Or that's my vague recollection, anyway.) At any rate, it's certainly a bit annoying, but you should call `plt.close(fig)` instead of `del fig`. On a side note, matplotlib could really use a context manager for this... – Joe Kington Feb 19 '14 at 15:24
  • 7
    @Hooked - To make it a bit more clear, you might edit your question to mention that `plt.close(fig)` will remove a specific figure instance from the pylab state machine (`plt._pylab_helpers.Gcf`) and allow it to be garbage collected. – Joe Kington Feb 19 '14 at 15:28
  • @JoeKington Thanks for the extra information, I've added your comment to the answer. I learned a little about the internals of pylab poking around the source to that call. – Hooked Feb 19 '14 at 15:55
  • 3
    @JoeKington `plt` is a bit of a mess and there are thoughts of how to re-do a bunch of it. Context manager is intriguing.... See https://github.com/matplotlib/matplotlib/pull/2736, https://github.com/matplotlib/matplotlib/pull/2624 – tacaswell Feb 19 '14 at 17:12
  • 1
    also,@Hooked Could you change the function calls to be `plt.*` as that is what they are calling and there is a low grade effort to kill `pylab` and convince people to stop doing `from matplotlib.pyplot import *` – tacaswell Feb 19 '14 at 17:13
  • @tcaswell You seem to be much more involved with the production of matplotlib than I. Please use (edit) my answer as a platform to advocate the usage you want to see. I'll use the edits as a template for further answers that I give concerning matplotlib. – Hooked Feb 19 '14 at 17:24
  • @tcaswell Thanks for making the answer better, the edits are fine. – Hooked Feb 19 '14 at 17:31
  • 1
    I tried every option mentioned here but I still get this warning – wuppi Mar 29 '16 at 15:10
  • `plt.subplots()` will create a new figure window by default. You can reuse the existing figure by passing its number: `fig, axarr = plt.subplots(..., num=plt.gcf().number)` (the `num` keyword is passed on to the figure() constructor.) – Åsmund Apr 16 '16 at 14:55
  • The [solution](https://stackoverflow.com/a/37336028/1938859) by Don Kirkby actually solves the memory issue, which is the real problem here. – Kyle Jul 18 '19 at 02:32
  • I used `plt.close(plt.gcf())` after `plt.savefig(...)` and it worked fine for me, no more error message (Python 3.6 Windows) – Hamman Samuel Feb 20 '20 at 03:19
  • Another alternative: If you `plt.figure(someFixedNumber, clear=True)`, then you'll continuously open the same figure, and clear out the old existing one every time. – Mark Storer Feb 20 '20 at 18:56
  • does this mean that I will get this warning if my code returns a figure? (since the one inside the function that was called didn't close the figure?) – Charlie Parker Sep 13 '21 at 20:02
  • `plt.close('all')` worked for me even though I was also using the `mplfinance` package to plot candlestick charts, ty :) @Hooked – NoahVerner Aug 19 '22 at 09:38
60

Here's a bit more detail to expand on Hooked's answer. When I first read that answer, I missed the instruction to call clf() instead of creating a new figure. clf() on its own doesn't help if you then go and create another figure.

Here's a trivial example that causes the warning:

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()

To avoid the warning, I have to pull the call to subplots() outside the loop. In order to keep seeing the rectangles, I need to switch clf() to cla(). That clears the axis without removing the axis itself.

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()

If you're generating plots in batches, you might have to use both cla() and close(). I ran into a problem where a batch could have more than 20 plots without complaining, but it would complain after 20 batches. I fixed that by using cla() after each plot, and close() after each batch.

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()

I measured the performance to see if it was worth reusing the figure within a batch, and this little sample program slowed from 41s to 49s (20% slower) when I just called close() after every plot.

Community
  • 1
  • 1
Don Kirkby
  • 53,582
  • 27
  • 205
  • 286
  • 2
    This is a great answer. The accepted answer does not really address the actual problem at hand, which is memory consumption. – Kyle Jul 18 '19 at 02:29
  • @Kyle: Why do you say that? The accepted answer says to clear a figure, which means another memory-consuming figure does not need to be made. It clearly fixes the memory problem. – jvriesem Sep 04 '21 at 03:08
  • Boy, 2019 must have been a bad year for me. :) – Kyle Sep 05 '21 at 11:21
39

If you intend to knowingly keep many plots in memory, but don't want to be warned about it, you can update your options prior to generating figures.

import matplotlib.pyplot as plt
plt.rcParams.update({'figure.max_open_warning': 0})

This will prevent the warning from being emitted without changing anything about the way memory is managed.

mightypile
  • 7,589
  • 3
  • 37
  • 42
  • in a Jupyter environment, does the memory allocation persist as long as the cell showing the plot exists? – matanster Aug 26 '18 at 15:34
  • 3
    @matanster, I would post that as it's own question. I started answering, then realized I really don't know enough about jupyter's management of kernels to answer honestly. – mightypile Aug 26 '18 at 15:47
  • @matanster All variables and memory allocated for them exist until the kernel is shut down explicitly by the user. It is not linked to cells. In newer Jupyter Hub, the system can shut down kernels (it can be configured). – greatvovan May 23 '20 at 00:55
4

The following snippet solved the issue for me:


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(...
# .....

When _wrapped_figure goes out of scope the runtime calls our __del__() method with plt.close() inside. It happens even if exception fires after _wrapped_figure constructor.

Dmitry
  • 2,989
  • 2
  • 24
  • 34
  • 2
    I liked this idea, however, if you are using pytest you cannot rely on the garbage collection to run. See discussion here: https://github.com/pytest-dev/pytest/discussions/8153#discussioncomment-214809 – marscher Oct 12 '21 at 17:59
3

This is also useful if you only want to temporarily suppress the warning:

import matplotlib.pyplot as plt
       
with plt.rc_context(rc={'figure.max_open_warning': 0}):
    lots_of_plots()
rwb
  • 4,309
  • 8
  • 36
  • 59
2
import matplotlib.pyplot as plt  
plt.rcParams.update({'figure.max_open_warning': 0})

If you use this, you won’t get that error, and it is the simplest way to do that.

ib.
  • 27,830
  • 11
  • 80
  • 100
saketh
  • 45
  • 1
  • 8
    So your solution is to completely ignore the warning? Could it be beneficial to understand what the warning is telling you (in this case, you have too many open figures), and address it (in this case, one might want to close a figure after it's done writing to it). This warning is often an indication that the client is accidentally opening additional figures, so being warned about it is a useful thing for plt to do. – rodrigo-silveira Jan 22 '21 at 18:25
2

matplotlib by default keeps a reference of all the figures created through pyplot. If a single variable used for storing matplotlib figure (e.g "fig") is modified and rewritten without clearing the figure, all the plots are retained in RAM memory. Its important to use plt.cla() and plt.clf() instead of modifying and reusing the fig variable. If you are plotting thousands of different plots and saving them without clearing the figure, then eventually your RAM will get exhausted and program will get terminated. Clearing the axes and figures have a significant impact on memory usage if you are plotting many figures. You can monitor your RAM consumption in task manager (Windows) or in system monitor (Linux). First your RAM will get exhausted, then the OS starts consuming SWAP memory. Once both are exhausted, the program will get automatically terminated. Its better to clear figures, axes and close them if they are not required.

YadneshD
  • 396
  • 2
  • 12