如何避免在Python 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

我编写了一个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)
导入破折号
从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以外的一些配置问题)。我今天也访问了这个链接,它描述了您的方法以及其他一些替代方法。