avvertimento su troppe figure aperte


166

In una sceneggiatura in cui creo molte figure fix, ax = plt.subplots(...), ricevo l'avvertimento RuntimeWarning: sono state aperte più di 20 figure. Le figure create tramite l'interfaccia pyplot ( matplotlib.pyplot.figure) vengono mantenute fino a quando non vengono esplicitamente chiuse e possono consumare troppa memoria.

Tuttavia, non capisco perché ricevo questo avviso, perché dopo aver salvato la figura con fig.savefig(...), la cancello con fig.clear(); del fig. In nessun punto del mio codice, ho più di una cifra aperta alla volta. Tuttavia, ricevo l'avvertimento su troppe figure aperte. Cosa significa / come posso evitare di ricevere l'avviso?


9
Se stai facendo molto di tutto questo e non stai visualizzando nulla in modo interattivo, potresti fare meglio a ignorare del plttutto. Ad esempio stackoverflow.com/a/16337909/325565 (Non inserire una delle mie risposte, ma è quella che ho trovato più rapidamente ...)
Joe Kington,

1
@JoeKington grazie questa è una soluzione migliore
hihell

La risposta di Joe Kington dovrebbe essere nell'elenco principale delle risposte. Funziona e risolve anche il problema con plt.close () che rallenta il programma menzionato da Don Kirby.
NatalieL,

Risposte:


199

Usa .clfo .clasul tuo oggetto figura invece di creare una nuova figura. Da @DavidZwicker

Supponendo che tu abbia importato pyplotcome

import matplotlib.pyplot as plt

plt.cla()cancella un asse , ovvero l'asse attualmente attivo nella figura corrente. Lascia intatti gli altri assi.

plt.clf()cancella l'intera figura corrente con tutti i suoi assi, ma lascia aperta la finestra, in modo che possa essere riutilizzata per altri grafici.

plt.close()chiude una finestra , che sarà la finestra corrente, se non diversamente specificato. plt.close('all')chiuderà tutte le figure aperte.

La ragione per cui del fignon funziona è che la pyplotmacchina a stati mantiene un riferimento alla figura intorno (come deve sapere se sta per conoscere la "figura attuale"). Ciò significa che anche se elimini il tuo riferimento alla figura, c'è almeno un riferimento attivo, quindi non sarà mai raccolto.

Dal momento che sto sondando la saggezza collettiva qui per questa risposta, @JoeKington menziona nei commenti che plt.close(fig)rimuoveranno un'istanza di figura specifica dalla macchina a stati pylab ( plt._pylab_helpers.Gcf ) e consentirò di essere spazzatura raccolta.


1
Mhh. C'è clfper la figureclasse, ma no close. Perché in del figrealtà non si chiude ed elimina la figura?
andreas-h

2
@ andreas-h La mia ipotesi: per qualcosa di complesso come un gestore di finestre con i propri gestori, potrebbe essere necessaria una maggiore pulizia rispetto al mettere qualcosa fuori campo. Il tuo diritto che closenon funzionerà sull'oggetto figura, chiamalo come plt.close(), invece di fig.clf().
Agganciato il

5
@ andreas-h - Fondamentalmente, la ragione per cui del fignon funziona è che dargli un __del__metodo (che sostanzialmente chiamerebbe plt.close(fig)) finirebbe per causare riferimenti circolari in questo caso particolare, e figavere un __del__metodo farà sì che altre cose non vengano raccolte . (O questo è il mio vago ricordo, comunque.) Ad ogni modo, è certamente un po 'fastidioso, ma dovresti chiamare plt.close(fig)invece di del fig. Per contro, matplotlib potrebbe davvero utilizzare un gestore di contesto per questo ...
Joe Kington,

6
@Hooked - Per renderlo un po 'più chiaro, potresti modificare la tua domanda per menzionare che plt.close(fig)rimuoverà un'istanza di figura specifica dalla macchina a stati pylab ( plt._pylab_helpers.Gcf) e consentirà di essere raccolta in modo inutile .
Joe Kington,

2
@JoeKington pltè un po 'un casino e ci sono pensieri su come rifarlo un po'. Il gestore del contesto è intrigante .... Vedi github.com/matplotlib/matplotlib/pull/2736 , github.com/matplotlib/matplotlib/pull/2624
tacaswell

33

Ecco un po 'più di dettaglio per espandere la risposta di Hooked . Quando ho letto per la prima volta quella risposta, ho perso l'istruzione di chiamare clf() invece di creare una nuova figura . clf()da solo non aiuta se poi vai a creare un'altra figura.

Ecco un esempio banale che provoca l'avvertimento:

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

Per evitare l'avvertimento, devo trasferire la chiamata subplots()all'esterno del loop. Per continuare a vedere i rettangoli, devo passare clf()a cla(). Ciò cancella l'asse senza rimuovere l'asse stesso.

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

Se stai generando grafici in lotti, potresti dover utilizzare sia cla()e close(). Ho riscontrato un problema in cui un batch poteva avere più di 20 grafici senza lamentarsi, ma si sarebbe lamentato dopo 20 batch. L'ho risolto usando cla()dopo ogni trama e close()dopo ogni partita.

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

Ho misurato le prestazioni per vedere se valeva la pena riutilizzare la cifra all'interno di un batch, e questo piccolo programma di esempio è rallentato da 41 a 49 secondi (20% più lento) quando ho appena chiamato close()dopo ogni trama.


Questa è un'ottima risposta La risposta accettata non risolve realmente il problema attuale, ovvero il consumo di memoria.
Kyle,

24

Se hai intenzione di mantenere consapevolmente molti grafici in memoria, ma non vuoi essere avvisato, puoi aggiornare le tue opzioni prima di generare cifre.

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

Ciò impedirà l'emissione dell'avviso senza modificare nulla sul modo in cui viene gestita la memoria.


in un ambiente Jupyter, l'allocazione della memoria persiste finché esiste la cella che mostra il grafico?
matanster,

2
@matanster, vorrei pubblicarlo come se fosse una domanda. Ho iniziato a rispondere, poi mi sono reso conto che davvero non so abbastanza della gestione dei kernel di Giove per rispondere onestamente.
poderoso

@matanster Tutte le variabili e la memoria allocate per loro esistono fino a quando il kernel non viene spento esplicitamente dall'utente. Non è collegato alle celle. Nel più recente Hub Jupyter, il sistema può chiudere i kernel (può essere configurato).
greatvovan

0

Il frammento seguente ha risolto il problema per 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(...
# .....

Quando _wrapped_figureesce dal campo di applicazione, il runtime chiama il nostro __del__()metodo con plt.close()inside. Succede anche se l'eccezione si attiva dopo il _wrapped_figurecostruttore.


0

Ciò è utile anche se si desidera solo eliminare temporaneamente l'avviso:

    import matplotlib.pyplot as plt
       
    with plt.rc_context(rc={'figure.max_open_warning': 0}):
        lots_of_plots()
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.