Python matplotlib: невозможно вызвать функцию FuncAnimation изнутри функции

Я пытаюсь реализовать функцию, которая выводит анимированный сюжет.

Если я возьму simple_anim.py(из примеров matplotlib) в качестве базы:

"""
 A simple example of an animated plot
"""
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation

fig, ax = plt.subplots()

x = np.arange(0, 2*np.pi, 0.01)        # x-array
line, = ax.plot(x, np.sin(x))

def animate(i):
    line.set_ydata(np.sin(x+i/10.0))  # update the data
    return line,

#Init only required for blitting to give a clean slate.
def init():
    line.set_ydata(np.ma.array(x, mask=True))
    return line,

ani = animation.FuncAnimation(fig, animate, np.arange(1, 200), init_func=init,
    interval=25, blit=True)
plt.show()

Эффективно работает.

НО, если я закрою этот код внутри функции (чтобы обеспечить изменение параметров и избежать выполнения явного файла для каждого возможного значения параметра):

"""
 A simple example of an animated plot
"""
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation

def a():
    fig, ax = plt.subplots()

    x = np.arange(0, 2*np.pi, 0.01)        # x-array
    line, = ax.plot(x, np.sin(x))

    def animate(i):
        line.set_ydata(np.sin(x+i/10.0))  # update the data
        return line,

    #Init only required for blitting to give a clean slate.
    def init():
        line.set_ydata(np.ma.array(x, mask=True))
        return line,

    ani = animation.FuncAnimation(fig, animate, np.arange(1, 200), init_func=init,
        interval=25, blit=True)
    plt.show()

а затем вызовите функцию, график рисунка останется белым. Фактически, он никогда не приходит, чтобы войти в одушевленную функцию.

Я знаю, что мне не хватает информации, и почему она не работает. Кто-нибудь может дать мне несколько советов?

Большое спасибо,

Andrés

Ответ 1

Причина, по которой это происходит, заключается в том, что таймеры и обратные вызовы, которые обновляют окно, являются атрибутами объекта ani. Если вы не держите ссылку на него, то ani в собранном мусоре и ваши таймеры/обратные вызовы исчезнут.

Решение состоит в том, чтобы вернуть вашу функцию ani и сохранить ссылку на нее в вашем коде:

def a(...):
    # stuff
    ani = animation.FuncAnimation(...)
    # more stuff
    return ani

outer_ani = a(...)

Эта проблема (см. github # 1656) обсуждалась, но не разрешалась.

Ответ 2

Как следует из хорошего ответа, поведение кода проблемы undefined, поскольку оно зависит от объекта, который был удален и доступен для сбора мусора.

На практике это поведение undefined проявляется по-разному с различными бэкэндами GUI. Для некоторых пользователей (например, этот "повторяющийся" вопрос), используя Wx-сервер в IDLE или в ярлыке Pylab по умолчанию в Windows, код undefined, похоже, работает ( Я говорю "кажется", потому что он действительно не работает, а скорее дает желаемые результаты на удачу). При работе в графическом интерфейсе Canopy с его базовым компонентом Qt по умолчанию код не работает. Qt и Wx имеют очень разные архитектуры и сборку мусора. (В Canopy бэкэнд GUI можно изменить на вкладке Python в меню "Настройки", если он изменен на Wx, тогда код undefined также работает, но опять же, это не делает его правильным.)

Ответ 3

As Исправлено 1656: Анимация - это сбор мусора, если явно не хранится # 8214, сборщик мусора Python удалит вашу анимацию, если она явно не сохранена.
Если вы хотите получить его в основной функции, вы можете сделать это так же, как @tacaswell. Однако, это не работает, если вы хотите сгенерировать анимацию в другой функции.
Мы можем определить глобальную переменную для хранения возвращаемого значения FuncAnimation.

global anim

def return_anim():
  anim = animation.FuncAnimation(fig, animate, blit=True)
  return anim

def use_ani():
  global anim
  anim = return_anim

use_ani()

Пожалуйста, не забудьте установить blit = True, иначе Python не будет перерисовывать вашу анимацию.