waarschuwing voor te veel open cijfers

In een script waarin ik veel figuren maak met fix, ax = plt.subplots(...), krijg ik de waarschuwing RuntimeWarning: Er zijn meer dan 20 figuren geopend. Cijfers die zijn gemaakt via de pyplot-interface (matplotlib.pyplot.figure) worden bewaard totdat ze expliciet worden gesloten en kunnen te veel geheugen in beslag nemen.

Ik begrijp echter niet waaromik deze waarschuwing krijg, want nadat ik het cijfer met fig.savefig(...)heb opgeslagen, verwijder ik het met fig.clear(); del fig. Op geen enkel moment in mijn code heb ik meer dan één figuur tegelijk open. Toch krijg ik de waarschuwing over te veel open cijfers. Wat betekent dat / hoe kan ik voorkomen dat ik de waarschuwing krijg?


Antwoord 1, autoriteit 100%

Gebruik .clfof .claop je figuurobject in plaats van een nieuwfiguur te maken. Van @DavidZwicker

Ervan uitgaande dat je pyplothebt geïmporteerd als

import matplotlib.pyplot as plt

plt.cla()wist een as, dwz de momenteel actieve as in de huidige figuur. Het laat de andere assen onaangeroerd.

plt.clf()wist de hele huidige figuurmet al zijn assen, maar laat het venster open, zodat het opnieuw kan worden gebruikt voor andere plots.

plt.close()sluit een venster, dat het huidige venster zal zijn, tenzij anders aangegeven. plt.close('all')sluit alle open cijfers.

De reden dat del figniet werkt, is dat de pyplotstate-machine een verwijzing naar de figuur in de buurt houdt (zoals het moet als het wil weten wat het ‘huidige cijfer’ is). Dit betekent dat zelfs als u uwref naar de figuur verwijdert, er ten minste één live ref is, dus deze zal nooit als afval worden verzameld.

Omdat ik hier de collectieve wijsheid voor dit antwoord peil, vermeldt @JoeKington in de opmerkingen dat plt.close(fig)verwijdert een specifieke figuurinstantie van de pylab-statusmachine (plt._pylab_helpers.Gcf) en laat het afval verzamelen.


Antwoord 2, autoriteit 19%

Hier is wat meer details om uit te breiden op Hooked’s antwoord. Toen ik dat antwoord voor het eerst las, miste ik de instructie om clf()aan te roepen in plaats van een nieuw figuur te maken. clf()alleen helpt niet als je dan een ander figuur gaat maken.

Hier is een triviaal voorbeeld dat de waarschuwing veroorzaakt:

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

Om de waarschuwing te vermijden, moet ik de aanroep naar subplots()buiten de lus trekken. Om de rechthoeken te blijven zien, moet ik clf()omschakelen naar cla(). Dat maakt de as vrij zonder de as zelf te verwijderen.

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

Als u plots in batches genereert, moet u mogelijk zowel cla()als close()gebruiken. Ik kwam een ​​probleem tegen waarbij een batch meer dan 20 plots kon hebben zonder te klagen, maar na 20 batches zou klagen. Ik heb dat opgelost door cla()te gebruiken na elke plot, en close()na elke 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()

Ik heb de prestaties gemeten om te zien of het de moeite waard was om het cijfer binnen een batch opnieuw te gebruiken, en dit kleine voorbeeldprogramma vertraagde van 41s naar 49s (20% langzamer) toen ik zojuist close()belde na elk perceel.


Antwoord 3, autoriteit 13%

Als u van plan bent om bewust veel plots in het geheugen te bewaren, maar er niet voor gewaarschuwd wilt worden, kunt u uw opties bijwerken voordat u cijfers genereert.

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

Dit voorkomt dat de waarschuwing wordt uitgezonden zonder iets te veranderen aan de manier waarop het geheugen wordt beheerd.


Antwoord 4

Dit is ook handig als u de waarschuwing slechts tijdelijk wilt onderdrukken:

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

Antwoord 5

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

Als je dit gebruikt, krijg je die foutmelding niet, en het is de eenvoudigste manier om dat te doen.


Antwoord 6

Het volgende fragment loste het probleem voor mij op:


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

Als _wrapped_figurebuiten bereik gaat, roept de runtime onze __del__()methode aan met plt.close()erin. Het gebeurt zelfs als de uitzondering wordt geactiveerd na de _wrapped_figure-constructor.

Other episodes