Продолжаем изучать 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)
В этом примере можно возвращать 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-приложении выполняются в следующие моменты:
- Как только запускается в браузере. Некоторые параметры инициализации callback, например, количество нажатий на кнопку, после запуска находятся в состояния None. Причём эти параметры перезаписывают то, что указно в layout;
- В момент прямого взаимодействия с пользователем. Нажатие на кнопку, выбор элемента в выпадающем списке или заполнение текста в блоке для ввода — все это вызывает callback;
- В момент косвенного взаимодействия с пользователем. Например, после ввода текста появляется график, а после него отображается ещё одно поле для ввода. Это похоже на цепную реакцию.
Предотвращаем начальное выполнение 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} кликов"
О способах визуализации данных в Dash для решения реальных задач Data Science, вы узнаете на специализированном курсе «VIP: Визуализация данных на языке Python» в лицензированном учебном центре обучения и повышения квалификации IT-специалистов в Москве.