2025, Dec 08 00:02

Как починить Slider в matplotlib: проблема областей видимости

Почему график в matplotlib с Slider не обновляется: ловушка областей видимости в колбэках и решение — передавайте значение во вспомогательную функцию.

Интерактивная визуализация с помощью Slider из matplotlib кажется простой, пока вы не разделите логику колбэков по функциям. Частая ловушка проявляется, когда вспомогательная функция читает переменную, которую, как вы ожидаете, обновляет Slider, но график остается застывшим в исходном состоянии. Разберем минимальный пример этой ловушки областей видимости и правильное решение.

Воспроизводим проблему

Цель — менять частоту синусоиды через Slider. График обновляется только если вся логика живет внутри колбэка Slider. Как только обновление делегируется во вспомогательную функцию, линия больше не меняется.

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider

xgrid = np.linspace(400, 800, 400)

figure, axis_main = plt.subplots()
figure.subplots_adjust(bottom=0.25)
factor = 1.0
wave_line, = axis_main.plot(xgrid, np.sin(factor * xgrid * 2 * np.pi / 500))

slider_axis = figure.add_axes([0.3, 0.1, 0.5, 0.04])
freq_slider = Slider(
    ax=slider_axis,
    label="modulation",
    valmin=0,
    valmax=20,
    valinit=1.0,
    orientation="horizontal"
)

def on_freq_change(val):
    factor = freq_slider.val
    update_curve()
    figure.canvas.draw_idle()


def update_curve():
    wave_line.set_ydata(np.sin(factor * xgrid * 2 * np.pi / 500))


freq_slider.on_changed(on_freq_change)

plt.show()

Что происходит на самом деле

Вспомогательная функция читает имя, не определенное в ее локальной области. В примере выше update_curve() использует factor. Поскольку factor не передается в update_curve() и никак иначе не объявляется, Python ищет снаружи и находит модульный factor = 1.0. Это значение никогда не меняется. Внутри колбэка on_freq_change() присваивание factor = freq_slider.val создает новую локальную переменную с именем factor, которая не влияет на модульную. В итоге update_curve() всегда видит исходный factor.

Правим логику колбэка

Передайте значение явно во вспомогательную функцию. Так поток данных становится однозначным, и график отражает текущее значение Slider.

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider

xgrid = np.linspace(400, 800, 400)

figure, axis_main = plt.subplots()
figure.subplots_adjust(bottom=0.25)
factor = 1.0
wave_line, = axis_main.plot(xgrid, np.sin(factor * xgrid * 2 * np.pi / 500))

slider_axis = figure.add_axes([0.3, 0.1, 0.5, 0.04])
freq_slider = Slider(
    ax=slider_axis,
    label="modulation",
    valmin=0,
    valmax=20,
    valinit=1.0,
    orientation="horizontal"
)

def refresh_curve(current_factor):
    wave_line.set_ydata(np.sin(current_factor * xgrid * 2 * np.pi / 500))


def on_freq_change(val):
    current = freq_slider.val
    refresh_curve(current)
    figure.canvas.draw_idle()


freq_slider.on_changed(on_freq_change)

plt.show()

Эта версия корректно обновляет линию, потому что refresh_curve() получает конкретное значение, а не полагается на поиск имени во внешней области. Есть и другой рабочий подход — объявить переменную глобальной внутри колбэка, — но явная передача значений делает зависимости понятными и локальными.

Почему это важно

Колбэки и области видимости часто пересекаются в интерактивной визуализации: по мере роста фигуры и разнесения логики интерфейса по помощникам опора на неявное разрешение имен становится хрупкой. Прямая передача значений предотвращает тихие баги, когда UI кажется отзывчивым, а график отражает устаревшее состояние. Это также упрощает рефакторинг: каждая функция явно сообщает о своих входах и больше не зависит от окружающего состояния.

Выводы

Создавая динамичные визуализации в matplotlib с Slider, следите за областью видимости переменных. Если помощнику требуется текущее значение Slider, передайте его явно. Эта небольшая дисциплина делает колбэки предсказуемыми, графики точными и позволяет легче масштабировать кодовую базу, когда математика за кривой усложняется.