avertissement concernant trop de chiffres ouverts

166

Dans un script où je crée de nombreuses figures avec fix, ax = plt.subplots(...), j'obtiens l'avertissement RuntimeWarning: Plus de 20 figures ont été ouvertes. Les figures créées via l'interface pyplot ( matplotlib.pyplot.figure) sont conservées jusqu'à ce qu'elles soient explicitement fermées et peuvent consommer trop de mémoire.

Cependant, je ne comprends pas pourquoi je reçois cet avertissement, car après avoir enregistré la figure avec fig.savefig(...), je la supprime avec fig.clear(); del fig. À aucun moment de mon code, j'ai plus d'un chiffre ouvert à la fois. Pourtant, je reçois l'avertissement concernant trop de chiffres ouverts. Qu'est-ce que cela signifie / comment puis-je éviter de recevoir l'avertissement?

andreas-h
la source
9
Si vous faites beaucoup de cela et que vous n'affichez rien de manière interactive, vous feriez peut-être mieux de contourner pltcomplètement. Par exemple, stackoverflow.com/a/16337909/325565 (Ne pas brancher une de mes propres réponses, mais c'est celle que j'ai pu trouver le plus rapidement ...)
Joe Kington
1
@JoeKington merci c'est une meilleure solution
saluthell
La réponse de Joe Kington devrait figurer dans la liste de réponses principale. Cela fonctionne et résout également le problème avec plt.close () qui ralentit le programme mentionné par Don Kirby.
NatalieL

Réponses:

199

Utilisez .clfou .clasur votre objet figure au lieu de créer une nouvelle figure. De @DavidZwicker

En supposant que vous ayez importé en pyplottant que

import matplotlib.pyplot as plt

plt.cla()efface un axe , c'est-à-dire l'axe actuellement actif dans la figure actuelle. Il laisse les autres axes intacts.

plt.clf()efface toute la figure actuelle avec tous ses axes, mais laisse la fenêtre ouverte, de sorte qu'elle puisse être réutilisée pour d'autres tracés.

plt.close()ferme une fenêtre , qui sera la fenêtre courante, sauf indication contraire. plt.close('all')fermera toutes les figures ouvertes.

La raison pour laquelle cela del figne fonctionne pas est que la pyplotmachine à états garde une référence à la figure autour (comme elle doit le faire si elle veut savoir quel est le «chiffre actuel»). Cela signifie que même si vous supprimez votre référence dans la figure, il y a au moins une référence en direct, elle ne sera donc jamais récupérée.

Puisque j'interroge ici sur la sagesse collective pour cette réponse, @JoeKington mentionne dans les commentaires qui plt.close(fig)supprimera une instance de figure spécifique de la machine à états pylab ( plt._pylab_helpers.Gcf ) et lui permettra d'être ramassé.

Accroché
la source
1
Mhh. Il y en a clfpour la figureclasse, mais pas close. Pourquoi ne del figferme et ne supprime pas réellement la figure?
andreas-h
2
@ andreas-h Je suppose: pour quelque chose de complexe comme un gestionnaire de fenêtres avec ses propres gestionnaires, il pourrait y avoir plus de nettoyage nécessaire que de mettre quelque chose hors de portée. Votre droit qui closene fonctionnera pas sur l'objet figure, appelez-le comme plt.close(), au lieu de fig.clf().
Accroché le
5
@ andreas-h - Fondamentalement, la raison pour laquelle cela del figne fonctionne pas est que lui donner une __del__méthode (qui appellerait fondamentalement plt.close(fig)) finirait par provoquer des références circulaires dans ce cas particulier, et figavoir une __del__méthode empêchera d'autres choses d'être ramassées. . (Ou c'est mon vague souvenir, en tout cas.) En tout cas, c'est certainement un peu ennuyeux, mais vous devriez appeler plt.close(fig)au lieu de del fig. Sur une note latérale, matplotlib pourrait vraiment utiliser un gestionnaire de contexte pour cela ...
Joe Kington
6
@Hooked - Pour rendre les choses un peu plus claires, vous pouvez modifier votre question pour mentionner que plt.close(fig)cela supprimera une instance de figure spécifique de la machine à états pylab ( plt._pylab_helpers.Gcf) et lui permettra d'être récupéré.
Joe Kington
2
@JoeKington pltest un peu en désordre et il y a des idées pour en refaire un tas. Le gestionnaire de contexte est intriguant .... Voir github.com/matplotlib/matplotlib/pull/2736 , github.com/matplotlib/matplotlib/pull/2624
tacaswell
33

Voici un peu plus de détails pour développer la réponse de Hooked . Quand j'ai lu cette réponse pour la première fois, j'ai manqué l'instruction d'appeler clf() au lieu de créer une nouvelle figure . clf()à lui seul, cela n'aide pas si vous allez ensuite créer une autre figure.

Voici un exemple trivial qui provoque l'avertissement:

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

Pour éviter l'avertissement, je dois tirer l'appel à l' subplots()extérieur de la boucle. Pour continuer à voir les rectangles, je dois passer clf()à cla(). Cela efface l'axe sans supprimer l'axe lui-même.

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

Si vous générez des graphiques par lots, vous devrez peut-être utiliser à la fois cla()et close(). J'ai rencontré un problème où un lot pouvait avoir plus de 20 parcelles sans se plaindre, mais il se plaindrait après 20 lots. J'ai corrigé cela en utilisant cla()après chaque parcelle et close()après chaque lot.

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

J'ai mesuré les performances pour voir s'il valait la peine de réutiliser la figure dans un lot, et ce petit programme d'exemple a ralenti de 41s à 49s (20% plus lent) lorsque je viens d'appeler close()après chaque tracé.

Don Kirkby
la source
C'est une excellente réponse. La réponse acceptée ne résout pas vraiment le problème actuel, qui est la consommation de mémoire.
Kyle
24

Si vous avez l'intention de garder sciemment de nombreux tracés en mémoire, mais que vous ne voulez pas en être averti, vous pouvez mettre à jour vos options avant de générer des chiffres.

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

Cela empêchera l'émission de l'avertissement sans rien changer à la façon dont la mémoire est gérée.

puissant
la source
dans un environnement Jupyter, l'allocation de mémoire persiste-t-elle tant que la cellule affichant le tracé existe?
matanster
2
@matanster, je publierais cela comme sa propre question. J'ai commencé à répondre, puis j'ai réalisé que je n'en sais vraiment pas assez sur la gestion des noyaux par Jupyter pour répondre honnêtement.
mightypile
@matanster Toutes les variables et la mémoire qui leur sont allouées existent jusqu'à ce que le noyau soit arrêté explicitement par l'utilisateur. Il n'est pas lié aux cellules. Dans Jupyter Hub plus récent, le système peut arrêter les noyaux (il peut être configuré).
greatvovan
0

L'extrait de code suivant a résolu le problème pour moi:


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

Quand il _wrapped_figureest hors de portée, le runtime appelle notre __del__()méthode avec plt.close()inside. Cela se produit même si l'exception se déclenche après le _wrapped_figureconstructeur.

Dmitry
la source
0

Ceci est également utile si vous souhaitez uniquement supprimer temporairement l'avertissement:

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