Продолжаем изучать 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-специалистов в Москве.





