如何避免在Python Dash中触发不需要的回调?
我编写了一个Dash应用程序,其源代码共享如下:如何避免在Python Dash中触发不需要的回调?,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__, e
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)
导入破折号
从dash导入dash
将dash_html_组件导入为html
将仪表板核心组件作为dcc导入
将dash_引导程序_组件作为dbc导入
从dash.dependencies导入输入,输出
导入时间
app=Dash(外部样式表=[dbc.themes.BOOTSTRAP],url\u base\u路径名='/self\u serve/'))
server=app.server
重置_标志=错误
计数器=0
app.title=‘堵塞的仪表板应用程序’
app.layout=html.Div([
#钮扣
dcc.Input(id='main',占位符='main Value'),
dcc.Input(id='filter1',占位符='filter1'),
dcc.Input(id='filter2',占位符='filter2'),
dcc.Input(id='filter3',占位符='filter3'),
dcc.Input(id='filter4',占位符='filter4'),
html.Div(id='output',children='')
])
#度量表
@app.callback(
输出(组件\u id='filter1',组件\u属性='value'),
输出(组件\u id='filter2',组件\u属性='value'),
输出(组件\u id='filter3',组件\u属性='value'),
输出(组件\u id='filter4',组件\u属性='value'),
[
输入(组件id='main',组件属性='value')
]
)
def更新过滤器(主):
#如果提供主过滤器,则清除所有过滤器
全局重置标志
重置_标志=真
返回“”、“”、“”、“”
@app.callback(
输出(组件id='Output',组件属性='children'),
[
输入(组件id='main',组件属性='value'),
输入(组件\u id='filter1',组件\u属性='value'),
输入(组件\u id='filter2',组件\u属性='value'),
输入(组件id='filter3',组件属性='value'),
输入(组件id='filter4',组件属性='value'),
]
)
def计算(*args):
#根据输入执行一些密集的计算,但我不希望清除操作不必要地触发此回调
ctx=dash.callback\u上下文
打印(“\n”)
打印(“**********内部计算****************”)
打印('已触发:',ctx.triggered)
打印('输入:',ctx.inputs)
#我解决这个问题的想法
全局重置_标志,计数器
如果重置_标志:
计数器+=1
如果计数器在您的情况下,您可能要做的是:
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
的状态并更新输出
对于时间密集型操作,您可能希望缓存这些结果。文档中有一个条目显示了这一点:这是我使用文件对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)
我不确定最佳做法是什么,但我会在布局中放置一个按钮Calculate
,只有当用户单击该按钮时才会触发昂贵的计算。您可以将过滤器等设置为状态
,而不是输入
,这样它们可以在回调中访问,但不会触发计算,是的,我想到了这个想法。在这种情况下,只有按钮将是唯一的输入,而其他过滤器和main将是State。但不幸的是,请求是尽量避免使用按钮,让计算动态完成。谢谢分享。我碰巧已经试过了。但问题是,一旦过滤器复位,过滤器就会触发“计算”回调。因此,这仍然是一个连锁的局面。但通过将过滤器设置为状态而不是输入,过滤器中的任何更新都不会触发计算。这是主要问题。谢谢分享。我想你还没有读第二部分。您应该缓存calculate
中的结果,以便它不会重新计算耗时较长的任务。仅此一项就可以省去使用计数器和其他东西的麻烦。嗨,皮特堡,谢谢分享你的想法。我非常同意最棘手的部分在于线程问题。也许我的示例代码(我对此不敏感)没有反映实际问题。问题是,每当程序流进行“繁重的计算”时,在实际代码中,它是pd.read_sql(),在快速连续调用时会出现错误,而不会让它返回数据帧。我认为在essense中,使用全局变量作为计数器和更新文件中的“标志”是相同的。核心问题是控制线程,以便以可控的顺序启动回调。您好,我的逻辑被证明是正确的,这很微妙,但它在我的PC上不起作用,而是在其他人的PC上起作用(可能是由于python以外的一些配置问题)。我今天也访问了这个链接,它描述了您的方法以及其他一些替代方法。