Warnung vor zu vielen offenen Zahlen

166

In einem Skript, mit dem ich viele Figuren erstelle fix, ax = plt.subplots(...), wird die Warnung RuntimeWarning angezeigt: Es wurden mehr als 20 Figuren geöffnet. Über die Pyplot-Schnittstelle ( matplotlib.pyplot.figure) erstellte Zahlen bleiben bis zum expliziten Schließen erhalten und verbrauchen möglicherweise zu viel Speicher.

Ich verstehe jedoch nicht, warum ich diese Warnung erhalte, da fig.savefig(...)ich sie nach dem Speichern mit lösche fig.clear(); del fig. Zu keinem Zeitpunkt in meinem Code ist mehr als eine Figur gleichzeitig geöffnet. Trotzdem bekomme ich die Warnung vor zu vielen offenen Zahlen. Was bedeutet das / wie kann ich die Warnung vermeiden?

andreas-h
quelle
9
Wenn Sie viel davon tun und nichts interaktiv anzeigen, ist es möglicherweise besser, alles zu umgehen plt. ZB stackoverflow.com/a/16337909/325565 (Nicht um eine meiner eigenen Antworten zu stecken , aber es ist die, die ich am schnellsten finden konnte ...)
Joe Kington
1
@ JoeKington danke dies ist eine bessere Lösung
hihell
Die Antwort von Joe Kington sollte in der Hauptantwortliste stehen. Es funktioniert und behebt auch das Problem, dass plt.close () das von Don Kirby erwähnte Programm verlangsamt.
NatalieL

Antworten:

198

Verwenden Sie .clfoder .claauf Ihrem Figurenobjekt, anstatt eine neue Figur zu erstellen . Von @DavidZwicker

Angenommen, Sie haben pyplotals importiert

import matplotlib.pyplot as plt

plt.cla()löscht eine Achse , dh die aktuell aktive Achse in der aktuellen Abbildung. Die anderen Achsen bleiben unberührt.

plt.clf()Löscht die gesamte aktuelle Figur mit all ihren Achsen, lässt jedoch das Fenster geöffnet, sodass es für andere Diagramme wiederverwendet werden kann.

plt.close()schließt ein Fenster , das das aktuelle Fenster ist, sofern nicht anders angegeben. plt.close('all')schließt alle offenen Figuren.

Der Grund, del figder nicht funktioniert, ist, dass die pyplotZustandsmaschine einen Verweis auf die herumliegende Figur behält (wie es sein muss, wenn sie wissen will, was die 'aktuelle Figur' ist). Dies bedeutet, dass selbst wenn Sie Ihren Verweis auf die Figur löschen , mindestens ein Live-Verweis vorhanden ist , sodass niemals Müll gesammelt wird.

Da ich hier nach der kollektiven Weisheit für diese Antwort frage, erwähnt @JoeKington in den Kommentaren, dass plt.close(fig)eine bestimmte Figureninstanz aus der Pylab-Zustandsmaschine ( plt._pylab_helpers.Gcf ) entfernt und das Sammeln von Müll ermöglicht wird.

Süchtig
quelle
1
Mhh. Es gibt clffür die figureKlasse, aber nicht close. Warum wird del figdie Figur nicht geschlossen und gelöscht?
Andreas-h
2
@ andreas-h Meine Vermutung: Für etwas Komplexes wie einen Fenstermanager mit eigenen Handlern ist möglicherweise mehr Bereinigung erforderlich, als etwas außerhalb des Gültigkeitsbereichs zu platzieren. Ihr Recht, das für das closeFigurenobjekt nicht funktioniert, nennen Sie es wie plt.close()statt fig.clf().
Hooked
5
@ andreas-h - Grundsätzlich del figfunktioniert dies nicht, weil die Angabe einer __del__Methode (die im Grunde genommen aufgerufen plt.close(fig)würde) in diesem speziellen Fall zu Zirkelverweisen führen würde und figeine __del__andere Methode dazu führen würde, dass andere Dinge nicht als Müll gesammelt werden . (Oder das ist sowieso meine vage Erinnerung.) Auf jeden Fall ist es ein bisschen nervig, aber Sie sollten plt.close(fig)stattdessen anrufen del fig. Nebenbei bemerkt, Matplotlib könnte wirklich einen Kontextmanager dafür verwenden ...
Joe Kington
6
@Hooked - Um es ein bisschen klarer zu machen, können Sie Ihre Frage so bearbeiten, dass erwähnt plt.close(fig)wird, dass eine bestimmte Figureninstanz von der Pylab-Zustandsmaschine ( plt._pylab_helpers.Gcf) entfernt und das Sammeln von Müll ermöglicht wird.
Joe Kington
2
@ JoeKington pltist ein bisschen chaotisch und es gibt Gedanken darüber, wie man ein paar davon noch einmal macht. Der Kontextmanager ist faszinierend ... Siehe github.com/matplotlib/matplotlib/pull/2736 , github.com/matplotlib/matplotlib/pull/2624
tacaswell
32

Hier ist ein bisschen mehr Detail, um die Antwort von Hooked zu erweitern . Als ich diese Antwort zum ersten Mal las, habe ich die Anweisung zum Anrufen verpasst, clf() anstatt eine neue Figur zu erstellen . clf()allein hilft nicht, wenn Sie dann gehen und eine andere Figur erstellen.

Hier ist ein triviales Beispiel, das die Warnung verursacht:

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

Um die Warnung zu vermeiden, muss ich den Anruf subplots()außerhalb der Schleife ziehen. Um die Rechtecke zu halten sehen, muss ich Schalter clf()auf cla(). Dadurch wird die Achse gelöscht, ohne die Achse selbst zu entfernen.

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

Wenn Sie Diagramme in Stapeln erstellen, müssen Sie möglicherweise beide cla()und verwenden close(). Ich stieß auf ein Problem, bei dem eine Charge mehr als 20 Parzellen haben konnte, ohne sich zu beschweren, aber sie beschwerte sich nach 20 Chargen. Ich habe das behoben, indem ich cla()nach jedem Plot und close()nach jeder Charge verwendet habe.

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

Ich habe die Leistung gemessen, um festzustellen, ob es sich lohnt, die Zahl innerhalb eines Stapels wiederzuverwenden, und dieses kleine Beispielprogramm verlangsamte sich von 41 auf 49 Sekunden (20% langsamer), als ich gerade close()nach jedem Plot anrief.

Don Kirkby
quelle
Dies ist eine großartige Antwort. Die akzeptierte Antwort geht nicht wirklich auf das eigentliche Problem ein, nämlich den Speicherverbrauch.
Kyle
24

Wenn Sie wissentlich viele Diagramme im Speicher behalten möchten, aber nicht gewarnt werden möchten, können Sie Ihre Optionen aktualisieren, bevor Sie Zahlen generieren.

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

Dadurch wird verhindert, dass die Warnung ausgegeben wird, ohne dass Änderungen an der Art und Weise vorgenommen werden, wie der Speicher verwaltet wird.

mächtig
quelle
Bleibt in einer Jupyter-Umgebung die Speicherzuordnung bestehen, solange die Zelle mit dem Plot vorhanden ist?
Matanster
2
@matanster, ich würde das als eigene Frage posten. Ich fing an zu antworten und stellte dann fest, dass ich wirklich nicht genug über Jupyters Kernel-Management weiß, um ehrlich zu antworten.
Mightypile
@matanster Alle ihnen zugewiesenen Variablen und Speicher sind vorhanden, bis der Kernel vom Benutzer explizit heruntergefahren wird. Es ist nicht mit Zellen verbunden. In neueren Jupyter Hub kann das System Kernel herunterfahren (es kann konfiguriert werden).
Greatvovan
0

Das folgende Snippet hat das Problem für mich gelöst:


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

Wenn _wrapped_figureder Gültigkeitsbereich überschritten wird, ruft die Laufzeit unsere __del__()Methode mit plt.close()inside auf. Dies geschieht auch dann, wenn eine Ausnahme nach dem _wrapped_figureKonstruktor ausgelöst wird .

Dmitry
quelle
0

Dies ist auch nützlich, wenn Sie die Warnung nur vorübergehend unterdrücken möchten:

    import matplotlib.pyplot as plt
       
    with plt.rc_context(rc={'figure.max_open_warning': 0}):
        lots_of_plots()
rwb
quelle