ostrzeżenie o zbyt wielu otwartych postaciach

166

W skrypcie, w którym tworzę wiele figurek fix, ax = plt.subplots(...), otrzymuję ostrzeżenie RuntimeWarning: Otwarto ponad 20 figurek. Figury utworzone za pomocą interfejsu pyplot ( matplotlib.pyplot.figure) są przechowywane do momentu jawnego zamknięcia i mogą zajmować zbyt dużo pamięci.

Nie rozumiem jednak, dlaczego otrzymuję to ostrzeżenie, bo po zapisaniu figury za pomocą fig.savefig(...), kasuję ją za pomocą fig.clear(); del fig. W żadnym momencie w moim kodzie nie mam otwartych więcej niż jednej cyfry naraz. Mimo to otrzymuję ostrzeżenie o zbyt wielu otwartych liczbach. Co to oznacza / jak mogę uniknąć ostrzeżenia?

andreas-h
źródło
9
Jeśli często to robisz i nie wyświetlasz niczego interaktywnie, może być lepiej, jeśli pltcałkowicie pominiesz. Np. Stackoverflow.com/a/16337909/325565 (Nie chcę podłączać jednej z moich odpowiedzi, ale to ta, którą znalazłem najszybciej ...)
Joe Kington
1
@JoeKington dziękuję, to lepsze rozwiązanie
hihell
Odpowiedź Joe Kingtona powinna znajdować się na głównej liście odpowiedzi. Działa i rozwiązuje problem z plt.close () spowalniającym program, o którym wspomniał Don Kirby.
NatalieL

Odpowiedzi:

199

Użyj .clflub .clana swoim obiekcie figury zamiast tworzyć nową figurę. Od @DavidZwicker

Zakładając, że zaimportowałeś pyplotjako

import matplotlib.pyplot as plt

plt.cla()czyści oś , tj. aktualnie aktywną oś na bieżącej figurze. Pozostawia pozostałe osie nietknięte.

plt.clf()czyści całą bieżącą figurę ze wszystkimi jej osiami, ale pozostawia otwarte okno, aby można było je ponownie wykorzystać na innych wykresach.

plt.close()zamyka okno , które będzie oknem bieżącym, jeśli nie określono inaczej. plt.close('all')zamknie wszystkie otwarte figury.

Przyczyną, del figktóra nie działa, jest to, że pyplotmaszyna stanu zachowuje odniesienie do figury wokół (tak jak musi, jeśli chce wiedzieć, jaka jest „bieżąca liczba”). Oznacza to, że nawet jeśli usuniesz swój odnośnik do figury, istnieje co najmniej jeden odnośnik na żywo, więc nigdy nie zostanie on usunięty .

Ponieważ sonduję tutaj zbiorową mądrość dla tej odpowiedzi, @JoeKington wspomina w komentarzach, plt.close(fig)które usuwają konkretną instancję figury z maszyny stanu pylab ( plt._pylab_helpers.Gcf ) i pozwalają na zbieranie śmieci.

Haczykowaty
źródło
1
Mhh. Jest clfna figurezajęcia, ale nie close. Dlaczego del figwłaściwie nie zamyka i nie usuwa figury?
andreas-h
2
@ andreas-h Domyślam się: w przypadku czegoś złożonego, takiego jak menedżer okien z własnymi programami obsługi, może być potrzebne więcej czyszczenia niż umieszczenie czegoś poza zakresem. Twoje prawo, closektóre nie będzie działać z obiektem figury, nazwij to jak plt.close()zamiast fig.clf().
Hooked
5
@ andreas-h - Zasadniczo powodem, del figktóry nie działa, jest to, że nadanie mu __del__metody (która w zasadzie plt.close(fig)wywołałaby) skończyłoby się, powodując cykliczne odwołania w tym konkretnym przypadku, a figposiadanie __del__metody spowoduje, że inne rzeczy nie będą zbierane jako śmieci . (W każdym razie to moje niejasne wspomnienie.) W każdym razie jest to trochę denerwujące, ale plt.close(fig)zamiast tego powinieneś zadzwonić del fig. Na marginesie, matplotlib może naprawdę użyć do tego menedżera kontekstu ...
Joe Kington,
6
@Hooked - Aby było to trochę jaśniejsze, możesz edytować swoje pytanie, aby wspomnieć, że plt.close(fig)usunie określoną instancję figury z maszyny stanu pylab ( plt._pylab_helpers.Gcf) i pozwoli na usunięcie jej jako śmieci.
Joe Kington,
2
@JoeKington pltjest trochę w bałaganie i zastanawiają się, jak zrobić to ponownie. Menedżer kontekstów jest intrygujący ... Zobacz github.com/matplotlib/matplotlib/pull/2736 , github.com/matplotlib/matplotlib/pull/2624
tacaswell
32

Oto nieco więcej szczegółów, aby rozwinąć odpowiedź Hookeda . Kiedy po raz pierwszy przeczytałem tę odpowiedź, przegapiłem instrukcję, aby zadzwonić clf() zamiast tworzyć nową figurę . clf()samo w sobie nie pomoże, jeśli potem pójdziesz i stworzysz kolejną figurę.

Oto trywialny przykład, który powoduje ostrzeżenie:

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

Aby uniknąć ostrzeżenia, muszę wyciągnąć połączenie subplots()poza pętlę. Aby nadal widzieć prostokąty, muszę przełączyć się clf()na cla(). To czyści oś bez usuwania samej osi.

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

Jeśli generujesz wykresy partiami, może być konieczne użycie obu cla()i close(). Napotkałem problem polegający na tym, że partia mogła mieć więcej niż 20 działek bez narzekania, ale po 20 partiach narzekała. Naprawiłem to, używając cla()po każdym wykresie i close()po każdej partii.

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

Zmierzyłem wydajność, aby sprawdzić, czy warto ponownie użyć liczby w partii, a ten mały przykładowy program zwolnił z 41 s do 49 s (20% wolniej), gdy dzwoniłem close()po każdym wykresie.

Don Kirkby
źródło
To świetna odpowiedź. Przyjęta odpowiedź tak naprawdę nie rozwiązuje aktualnego problemu, jakim jest zużycie pamięci.
Kyle
24

Jeśli zamierzasz świadomie przechowywać wiele wykresów w pamięci, ale nie chcesz być o tym ostrzegany, możesz zaktualizować swoje opcje przed wygenerowaniem liczb.

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

Zapobiegnie to wysyłaniu ostrzeżenia bez zmiany sposobu zarządzania pamięcią.

mightypile
źródło
w środowisku Jupyter, czy alokacja pamięci utrzymuje się tak długo, jak długo istnieje komórka przedstawiająca wykres?
matanster
2
@matanster, opublikowałbym to jako własne pytanie. Zacząłem odpowiadać, ale zdałem sobie sprawę, że naprawdę nie wiem wystarczająco dużo o zarządzaniu jądrem przez jupyter, aby odpowiedzieć szczerze.
mightypile
@matanster Wszystkie zmienne i przydzielona im pamięć istnieją do momentu jawnego wyłączenia jądra przez użytkownika. Nie jest powiązany z komórkami. W nowszym Jupyter Hub system może wyłączać jądra (można to skonfigurować).
greatvovan
0

Poniższy fragment kodu rozwiązał problem za mnie:


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

Kiedy _wrapped_figurewychodzi poza zakres, środowisko wykonawcze wywołuje naszą __del__()metodę z plt.close()inside. Dzieje się tak, nawet jeśli wyjątek jest uruchamiany po _wrapped_figurekonstruktorze.

Dmitry
źródło
0

Jest to również przydatne, jeśli chcesz tylko tymczasowo wyłączyć ostrzeżenie:

    import matplotlib.pyplot as plt
       
    with plt.rc_context(rc={'figure.max_open_warning': 0}):
        lots_of_plots()
rwb
źródło