попередження про занадто багато відкритих фігур


166

У сценарії, де я створюю багато фігур fix, ax = plt.subplots(...), отримую попередження RuntimeWarning: Більше 20 фігур було відкрито. Фігури, створені через інтерфейс pyplot ( matplotlib.pyplot.figure), зберігаються до явного закриття і можуть зайняти занадто багато пам'яті.

Однак я не розумію, чому я отримую це попередження, тому що після збереження фігури fig.savefig(...), я видаляю її fig.clear(); del fig. У жодному моєму коді я одночасно відкриваю не одну фігуру. І все-таки я отримую попередження про занадто багато відкритих фігур. Що це означає / як я можу уникнути отримання попередження?


9
Якщо ви багато цього робите і не показуєте нічого інтерактивно, вам може бути краще пройти повний обхід plt. Напр. Stackoverflow.com/a/16337909/325565 (Не підключати жодної моєї відповіді, але це я, яку я міг би знайти найшвидше ...)
Джо Кінгтон,

1
@JoeKington дякую, що це краще рішення
hihell

Відповідь Джо Кінгтона має бути в головному списку відповідей. Це працює, а також вирішує проблему, коли plt.close () уповільнює програму, про яку згадував Дон Кірбі.
NatalieL

Відповіді:


199

Використовуйте .clfабо .claна об'єкті фігури замість створення нової фігури. Від @DavidZwicker

Якщо припустити, що ви імпортували pyplotяк

import matplotlib.pyplot as plt

plt.cla()очищає вісь , тобто поточну активну вісь у поточній фігурі. Інші осі залишає недоторканими.

plt.clf()очищає всю поточну фігуру з усіма її осями, але залишає вікно відкритим, щоб його можна було повторно використовувати для інших сюжетів.

plt.close()закриває вікно , яке буде поточним вікном, якщо не вказано інше. plt.close('all')закриє всі відкриті фігури.

Причина, del figяка не працює, полягає в тому, що pyplotдержавна машина зберігає посилання на фігуру навколо (як це має бути, якщо вона буде знати, що таке "поточна цифра"). Це означає, що навіть якщо ви видалите свою номенклатуру на фігуру, є хоча б одна реальна інформація, отже, вона ніколи не збиратиме сміття.

Оскільки я опитую тут колективну мудрість для цієї відповіді, @JoeKington в коментарях згадує, що plt.close(fig)видалить конкретний екземпляр фігури з державної машини pylab ( plt._pylab_helpers.Gcf ) і дозволить збирати сміття.


1
Mhh. Є clfдля figureкласу, але ні close. Чому del figнасправді фігура не закривається та не видаляється?
andreas-h

2
@ andreas-h Моя здогадка: для чогось такого складного, як диспетчер вікон із власними обробниками, може знадобитися більше очищення, ніж виведення чогось поза рамки. Ваше право, яке closeне працюватиме на об'єкті фігури, називайте це як plt.close(), а не fig.clf().
Зачепили

5
@ andreas-h - В основному, причина, del figяка не працює, полягає в тому, що надання йому __del__методу (який би в основному викликав plt.close(fig)би) закінчиться, викликаючи циркулярні посилання в цьому конкретному випадку, а figнаявність __del__методу призведе до того, що інші речі не збираються сміттям . (Або все-таки це мій неясний спогад.) У будь-якому випадку це, звичайно, трохи дратує, але вам варто подзвонити plt.close(fig)замість цього del fig. Зі сторони, Matplotlib дійсно може використовувати контекстний менеджер для цього ...
Джо Кінгтон,

6
@Hooked - Щоб зробити це трохи більш зрозумілим, ви можете відредагувати своє запитання, щоб згадати, що plt.close(fig)видалить конкретну інстанцію фігури зі стану машини pylab ( plt._pylab_helpers.Gcf) і дозволить її збирати сміття.
Джо Кінгтон,

2
@JoeKington pltтрохи заплутався, і є думки про те, як знову зробити купу. Менеджер контексту інтригує .... Дивіться github.com/matplotlib/matplotlib/pull/2736 , github.com/matplotlib/matplotlib/pull/2624
tacaswell

32

Ось трохи детальніше, щоб розкрити відповідь Хукеда . Коли я вперше прочитав цю відповідь, я пропустив інструкцію дзвонити, clf() а не створювати нову фігуру . clf()самостійно не допомагає, якщо потім перейти до створення іншої фігури.

Ось тривіальний приклад, який викликає попередження:

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

Щоб уникнути попередження, я повинен витягнути виклик до subplots()зовнішньої петлі. Щоб продовжувати бачити прямокутники, мені потрібно перейти clf()до cla(). Це очищає вісь, не знімаючи саму вісь.

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

Якщо ви генеруєте ділянки партіями, можливо, вам доведеться використовувати і те, cla()і close(). Я зіткнувся з проблемою, коли партія може мати більше 20 ділянок без нарікань, але вона скаржиться після 20 партій. Я це виправив, використовуючи cla()після кожної ділянки та close()після кожної партії.

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

Я виміряв продуктивність, щоб побачити, чи варто повторно використовувати фігуру в рамках партії, і ця маленька вибіркова програма сповільнилася з 41s до 49s (на 20% повільніше), коли я щойно дзвонив close()після кожного сюжету.


Це чудова відповідь. Прийнята відповідь насправді не вирішує фактичну проблему, яка полягає у споживанні пам'яті.
Кайл

24

Якщо ви маєте намір свідомо зберігати багато сюжетів у пам’яті, але не хочете, щоб про це попередили, ви можете оновити свої параметри до створення фігур.

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

Це не дозволить видавати попередження, не змінюючи нічого про спосіб управління пам'яттю.


чи існує розподіл пам’яті в середовищі Юпітера, поки існує клітина, що показує графік?
matanster

2
@matanster, я б написав це як власне питання. Я почав відповідати, потім зрозумів, що насправді я не знаю достатньо про управління ядрами, щоб відповісти чесно.
могучий

@matanster Всі змінні та виділена для них пам'ять існують, поки ядро ​​явно не вимкнеться користувачем. Він не пов'язаний з клітинами. У новіших Jupyter Hub система може відключити ядра (це можна налаштувати).
greatvovan

0

Наступний фрагмент вирішив проблему для мене:


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

Коли програма _wrapped_figureвиходить за межі, програма виконує виклики нашого __del__()методу plt.close()всередині. Це трапляється навіть у тому випадку, якщо виключення спрацьовує після _wrapped_figureконструктора.


0

Це також корисно, якщо ви хочете лише тимчасово придушити попередження:

    import matplotlib.pyplot as plt
       
    with plt.rc_context(rc={'figure.max_open_warning': 0}):
        lots_of_plots()
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.