#python #plotly-dash
#python #plotly-dash
Вопрос:
Я написал приложение Dash, исходный код которого приведен ниже:
import dash
from dash import Dash
import dash_html_components as html
import dash_core_components as dcc
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output
import time
app = Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP], url_base_pathname='/self_serve/')
server = app.server
reset_flag = False
counter = 0
app.title = 'jammed dash app'
app.layout = html.Div([
# buttons
dcc.Input(id='main',placeholder='Main Value'),
dcc.Input(id='filter1',placeholder='filter 1'),
dcc.Input(id='filter2',placeholder='filter 2'),
dcc.Input(id='filter3',placeholder='filter 3'),
dcc.Input(id='filter4',placeholder='filter 4'),
html.Div(id='output',children='')
])
#metric list
@app.callback(
Output(component_id='filter1',component_property='value'),
Output(component_id='filter2',component_property='value'),
Output(component_id='filter3',component_property='value'),
Output(component_id='filter4',component_property='value'),
[
Input(component_id='main',component_property='value')
]
)
def update_filter(main):
# clear up all filters if main is provided
global reset_flag
reset_flag = True
return '','','',''
@app.callback(
Output(component_id='output',component_property='children'),
[
Input(component_id='main',component_property='value'),
Input(component_id='filter1',component_property='value'),
Input(component_id='filter2',component_property='value'),
Input(component_id='filter3',component_property='value'),
Input(component_id='filter4',component_property='value'),
]
)
def calculate(*args):
# do some intensive calculation based on the inputs, but I do not want the clearing action to trigger this callback undesirably
ctx = dash.callback_context
print('n')
print('************ inside calculate *************')
print('triggered:', ctx.triggered)
print('inputs:', ctx.inputs)
# my idea for solving this problem
global reset_flag, counter
if reset_flag:
counter = 1
if counter <= 4:
print('counter:',counter)
print('reset_flag:',reset_flag)
return ''
else:
reset_flag = False
counter = 0
print('we passed into the correct flow!')
pass
# below is some intensive calculation using pandas.read_sql(), substituted by time.sleep()
print('Wait 10 seconds here')
time.sleep(10)
output = ''
for item in args:
if item:
output = item
print('output:',output)
return output
if __name__ == '__main__':
app.run_server(debug=True)
Мне нужно выполнить некоторые интенсивные вычисления (например, код sql) во 2-м обратном вызове («вычислить»). Я также хотел бы очищать все элементы фильтра всякий раз, когда изменяется значение в «main», что реализовано в 1-м обратном вызове («update_filter»).
Проблема в том, что, когда каждый фильтр очищается при 1-м обратном вызове, dash быстро вызывает 2-й обратный вызов, и программа застревает.
Мой вопрос: как избежать запуска 2-го обратного вызова при вызове 1-го обратного вызова? Моя идея состоит в том, чтобы отслеживать, сколько раз 2-й обратный вызов вызывается «избыточно» и позволяет передавать «правильный» вызов, возвращая пустой вывод для «избыточных» вызовов. Я пытался реализовать использование глобального счетчика, но это безрезультатно.
И вообще, какова наилучшая / обычная практика для предотвращения этих нежелательных, связанных обратных вызовов?
Заранее большое спасибо!
Комментарии:
1. Я не уверен, какова наилучшая практика, но я бы поместил кнопку
Calculate
в ваш макет и запустил бы ваши дорогостоящие вычисления только тогда, когда пользователь нажимает на эту кнопку. Вы можете настроить свои фильтры и т.State
Д. Так,Input
Чтобы они были доступны в вашем обратном вызове, Но dot запускает вычисление, community.plotly.com/t /…2. Да, я думал об этой идее. В этом случае только кнопка будет единственным входом, в то время как другие фильтры и main будут состоянием. Но, к сожалению, запрос заключается в том, чтобы попытаться избежать использования button и позволить вычислению выполняться динамически. В любом случае спасибо за обмен.
Ответ №1:
Вот моя довольно грубая реализация блокировки вашей calculate
функции с использованием файла. Я думаю, что глобальные переменные проблематичны, учитывая различные проблемы с потоками и т.д. Идея заключается в том, что при calculate
выполнении своих тяжелых вычислений он помещает ‘1’ в файл, а когда это делается, он помещает туда ‘0’. Если тем временем функция вызывается снова, она проверяет файл блокировки, и если там есть ‘1’, он существует без запуска пересчета.
import dash
from dash import Dash
import dash_html_components as html
import dash_core_components as dcc
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output
import time
app = Dash(__name__, external_stylesheets=[
dbc.themes.BOOTSTRAP], url_base_pathname='/self_serve/')
server = app.server
reset_flag = False
counter = 0
lockfilename = 'lockfile.txt' # this is where we keep the lock
with open(lockfilename, 'w') as fw:
fw.write('0') # unlock when we start
app.title = 'jammed dash app'
app.layout = html.Div([
# buttons
dcc.Input(id='main', placeholder='Main Value'),
dcc.Input(id='filter1', placeholder='filter 1'),
dcc.Input(id='filter2', placeholder='filter 2'),
dcc.Input(id='filter3', placeholder='filter 3'),
dcc.Input(id='filter4', placeholder='filter 4'),
html.Div(id='output', children='')
])
# metric list
@app.callback(
Output(component_id='filter1', component_property='value'),
Output(component_id='filter2', component_property='value'),
Output(component_id='filter3', component_property='value'),
Output(component_id='filter4', component_property='value'),
[
Input(component_id='main', component_property='value')
]
)
def update_filter(main):
# clear up all filters if main is provided
global reset_flag
reset_flag = True
return '', '', '', ''
@app.callback(
Output(component_id='output', component_property='children'),
[
Input(component_id='main', component_property='value'),
Input(component_id='filter1', component_property='value'),
Input(component_id='filter2', component_property='value'),
Input(component_id='filter3', component_property='value'),
Input(component_id='filter4', component_property='value'),
]
)
def calculate(*args):
# do some intensive calculation based on the inputs, but I do not want the clearing action to trigger this callback undesirably
ctx = dash.callback_context
print('n')
print('************ inside calculate *************')
print('triggered:', ctx.triggered)
print('inputs:', ctx.inputs)
with open(lockfilename, 'r') as fr:
line = next(fr)
if(line[0] == '1'):
print('Calc is locked, early exit')
return dash.no_update
# below is some intensive calculation using pandas.read_sql(), substituted by time.sleep()
print('Starting heavy calc, locking the file')
with open(lockfilename, 'w') as fw:
fw.write('1')
print('Wait 10 seconds here')
for n in range(10):
print('.', end='')
time.sleep(1)
output = ''
for item in args:
if item:
output = item
print('output:', output)
print('Done with heavy calc, unlocking the file')
with open(lockfilename, 'w') as fw:
fw.write('0')
return output
if __name__ == '__main__':
app.run_server(debug=True)
Комментарии:
1. Привет, питербарг, спасибо, что поделились своими мыслями. Я полностью согласен с тем, что самая сложная часть заключается в проблемах с потоками. Возможно, мой примерный код (который я уменьшил чувствительность) не отражает реальную проблему. Дело в том, что всякий раз, когда поток программы переходит к «тяжелому вычислению», в реальном коде это pd.read_sql() , который выдает ошибку при вызове в быстрой последовательности, не позволяя ему возвращать фрейм данных. Я думаю, по сути, использование глобальной переменной в качестве счетчика и обновление «флага» в файле одинаковы. Основная проблема заключается в управлении потоками, чтобы обратный вызов выполнялся в контролируемой последовательности.
2. Еще раз привет, очень тонко, что моя логика оказалась правильной, но она работает не на моем компьютере, а на чужом (вероятно, из-за некоторых проблем с конфигурацией за пределами python). Сегодня я также перешел по этой ссылке community.plotly.com/t/sharing-a-dataframe-between-plots/6173 , который описывает ваш метод, а также некоторые другие альтернативы.
Ответ №2:
В вашем случае то, что вы, возможно, захотите сделать, это:
Input(main) -> Output(filter) # reset filter
# when filter changes, take main's state and use it to update output
Input(filter), State(main) -> Output(output)
Это не блокируется, потому что это цепной обратный вызов: изменение main
очищается filter
, а изменение filter
запускает второй обратный вызов, который принимает main
состояние и обновляет output
.
Для трудоемких операций вы можете захотеть кэшировать эти результаты. В документе есть запись, показывающая это: https://dash.plotly.com/performance
Комментарии:
1. Так получилось, что я уже пробовал это. Но проблема в том, что, как только фильтр будет сброшен, filter запустит обратный вызов «вычисления». Таким образом, это все еще сложная ситуация. Но, установив для filter значение State вместо Input, любое обновление только в filter не вызовет вычисление. Это основная проблема. В любом случае спасибо, что поделились.
2. Я не думаю, что вы еще не прочитали вторую часть. Вы должны кэшировать свои результаты
calculate
, чтобы он не пересчитывал трудоемкую задачу. Это само по себе избавит от необходимости использовать счетчики и прочее.