Контроль над callback в Dash

Продолжаем изучать Python-фреймворк визуализации данных Dash. Сегодня мы расскажем о работе обратных вызовов (callback) в Dash. Читайте у нас: игнорирование обновления, частичное обновление callback, а также выяснение момента выполнения callback.

Игнорирование обновления обратного вызова

В некоторых ситуациях не нужно отображать результат на веб-странице сразу, т.е. декорируемая функция не должна ничего возвращать. Например, только после нажатия на кнопку должно что-то появиться. Тогда используется исключение PreventUpdate. Ниже представлен код на Python, где обновление callback не происходит, пока не нажата кнопка.

import dash
import dash_html_components as html
from dash.dependencies import Input, Output
from dash.exceptions import PreventUpdate


app = dash.Dash(__name__)

app.layout = html.Div([
    html.Button('Нажмите, чтобы посмотреть', id='show-secret'),
    html.Div(id='body-div')
])

@app.callback(
    Output(component_id='body-div', component_property='children'),
    [Input(component_id='show-secret', component_property='n_clicks')]
)
def update_output(n_clicks):
    if n_clicks is None:
        raise PreventUpdate
    else:
        return f"Было произведено {n_clicks} кликов"


app.run_server(debug=True)
Содержимое появится только после нажатия Dash
Использование PreventUpdate

В этом примере можно возвращать None — результат будет тот же. Но в случае если имеется множество Output’ов, то придётся возвращать такое же количество None, поэтому лучше воспользоваться PreventUpdate. К тому же, отлаживать callback с None будет гораздо сложнее, особенно если ваш Data Science проект начнет разрастаться, поэтому также стоит обратить внимание на частичное обновление.

Частичное обновление обратных вызовов

В Dash если у вас имеется несколько Output’ов, но при определённых условиях вы хотите обновлять только один из них, то dash.no_update заменит вам None. Ниже пример callback’а на Python, где при значении Input равным 1, обновляется только второй Output.

@app.callback(
    [Output('out', 'children'), Output('err', 'children')],
    [Input('num', 'value')]
)
def show_factors(num):
    if num is None:
        raise dash.exceptions.PreventUpdate

    if num == 1:
        return dash.no_update, '{} - это единица!'.format(num)

    return '{} - это больше единицы!'.format(num), ''

Определяем, какой Input был задействован

В Dash есть глобальная переменная dash.callback_context, доступная только внутри callback. У нее есть атрибуты:

  • triggered — список текущих обновляемых компонент. Он изменяется каждый раз, когда происходит переключение с одного Input на другой.
  • inputs и states позволяют получить доступ к идентификатору и к значению (id) Input или State.

Ниже пример Dash-приложения, где имеется 3 кнопки, а на странице отображаются текущее количество кликов, а также dash.callback_context в формате JSON (JavaScript Object Notation).

app.layout = html.Div([
    html.Button('Button 1', id='btn-1'),
    html.Button('Button 2', id='btn-2'),
    html.Button('Button 3', id='btn-3'),
    html.Div(id='container')
])


@app.callback(Output('container', 'children'),
              [Input('btn-1', 'n_clicks'),
               Input('btn-2', 'n_clicks'),
               Input('btn-3', 'n_clicks')])
def display(btn1, btn2, btn3):
    ctx = dash.callback_context

    if not ctx.triggered:
        button_id = 'No clicks yet'
    else:
        button_id = ctx.triggered[0]['prop_id'].split('.')[0]

    ctx_msg = json.dumps({
        'states': ctx.states,
        'triggered': ctx.triggered,
        'inputs': ctx.inputs
    }, indent=2)

    return html.Div([
        html.Table([
            html.Tr([html.Th('Button 1'),
                     html.Th('Button 2'),
                     html.Th('Button 3'),
                     html.Th('Most Recent Click')]),
            html.Tr([html.Td(btn1 or 0),
                     html.Td(btn2 or 0),
                     html.Td(btn3 or 0),
                     html.Td(button_id)])
        ]),
        html.Pre(ctx_msg)
    ])
Кнопки и количество кликов Dash
Отображение на веб-странице dash.callback_context

В какой момент выполняются обратные вызовы

Все обратные вызовы в Dash-приложении выполняются в следующие моменты:

  1. Как только запускается в браузере. Некоторые параметры инициализации callback, например, количество нажатий на кнопку, после запуска находятся в состояния None. Причём эти параметры перезаписывают то, что указно в layout;
  2. В момент прямого взаимодействия с пользователем. Нажатие на кнопку, выбор элемента в выпадающем списке или заполнение текста в блоке для ввода — все это вызывает callback;
  3. В момент косвенного взаимодействия с пользователем. Например, после ввода текста появляется график, а после него отображается ещё одно поле для ввода. Это похоже на цепную реакцию.

Предотвращаем начальное выполнение callback

В первом пункте мы указали, что после запуска Dash-приложения в браузере сразу же выполняется callback с перезаписыванием параметров layout. Такое поведение можно изменить, если передать в callback ещё один аргумент prevent_initial_call=True. Ниже представлен пример на Python, где Dash-приложение ожидает нажатия кнопки, чтобы изменить значения, указанные в layout.

app.layout = html.Div([
    html.Button('Нажми', id='inp-btn'),
    html.Div(id='out', children='Начальное значение'),
])

@app.callback(
    Output(component_id='out', component_property='children'),
    [Input(component_id='inp-btn', component_property='n_clicks')],
    prevent_initial_call=True
)
def update_output(n_clicks):
    return f"Было произведено {n_clicks} кликов"

Мгновенное обновление callback Dash
Результат при prevent_initial_call=False (по умолчанию)

Обновление callback после нажатия Dash
Результат при prevent_initial_call=True

 

О способах визуализации данных в Dash для решения реальных задач Data Science, вы узнаете на специализированном курсе «VIP: Визуализация данных на языке Python» в лицензированном учебном центре обучения и повышения квалификации IT-специалистов в Москве.

Источники
  1. https://dash.plotly.com/advanced-callbacks

Добавить комментарий

Поиск по сайту