предупреждение о слишком большом количестве открытых фигур

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самом деле не закрыть и не удалить фигуру?
Андреас-ч
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
33

Вот немного подробнее, чтобы расширить ответ Хукеда . Когда я впервые прочитал этот ответ, я пропустил инструкцию для вызова 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()

Я измерил производительность, чтобы увидеть, стоит ли повторно использовать цифру в пакете, и эта небольшая программа-пример замедлилась с 41 до 49 с (на 20% медленнее), когда я только что звонил close()после каждого графика.

Дон Киркби
источник
Это отличный ответ. Принятый ответ на самом деле не решает актуальную проблему - потребление памяти.
Кайл
24

Если вы намереваетесь сознательно хранить много графиков в памяти, но не хотите, чтобы об этом предупреждали, вы можете обновить свои параметры до создания цифр.

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

Это предотвратит выдачу предупреждения без каких-либо изменений в способе управления памятью.

mightypile
источник
сохраняется ли выделение памяти в среде Jupyter до тех пор, пока существует ячейка, показывающая график?
matanster
2
@matanster, я бы опубликовал это как собственный вопрос. Я начал отвечать, но потом понял, что действительно недостаточно знаю об управлении ядром в Jupyter, чтобы отвечать честно.
mightypile
@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()inside. Это происходит, даже если исключение срабатывает после _wrapped_figureконструктора.

Дмитрий
источник
0

Это также полезно, если вы хотите временно отключить предупреждение:

    import matplotlib.pyplot as plt
       
    with plt.rc_context(rc={'figure.max_open_warning': 0}):
        lots_of_plots()
РБГ
источник