在 Dash 中创建无限回调循环

问题描述 投票:0回答:2

我正在尝试在破折号中创建回调循环。我的想法是有一个按钮,它会重新点击自身,直到我们点击 n_clicks = 10。

import time

import dash
import dash_bootstrap_components as dbc

from dash import Dash, html, Input, Output

app = Dash(__name__,
           external_stylesheets=[dbc.themes.BOOTSTRAP])

app.layout = html.Div([
    html.Button(children='Run Process', id='run-button', n_clicks=0),
    html.Div(id='output-container')
])

@app.callback(
    Output('run-button', 'n_clicks'),
    Output('output-container', 'children'),
    Input('run-button', 'n_clicks'),
)
def run_process(n_clicks):
    if n_clicks > 10:
        return dash.no_update

    print(n_clicks)
    time.sleep(1)
    return n_clicks + 1, n_clicks + 1


if __name__ == '__main__':
    app.run_server(debug=True, port=100)

但是 print(n_clicks) 打印 0,然后打印 2(跳过 1?!),然后停止,应该一直打印到 10。 知道如何修复以及发生了什么事吗?

python loops callback plotly-dash
2个回答
1
投票

也许你应该使用计时器

dcc.Interval(..., interval=1 * 1000, max_intervals=10, disabled=True)

button
应将
disabled
更改为
False
以启动计时器。

它应该分配运行代码的回调。

类似这样的:

import dash
from dash import dcc
#import dash_bootstrap_components as dbc
from dash import Dash, html, Input, Output

app = Dash(__name__)#, external_stylesheets=[dbc.themes.BOOTSTRAP])

app.layout = html.Div([
    html.Button(id='run-button', children='Run Process', n_clicks=0),
    dcc.Interval(id="interval-component", interval=1 * 1000, max_intervals=10, disabled=True),
    html.H1(id='label', children=''),    
])

@app.callback(
    Output("interval-component", "disabled"), 
    Input('run-button', 'n_clicks'),
)
def run_process(n_clicks):
    if n_clicks > 0:
        return False

    return True
    
@app.callback(
    Output('label', 'children'),
    Input('interval-component', 'n_intervals'),
)
def update_interval(n_intervals):
    return f'Intervals Passed: {n_intervals}'

if __name__ == '__main__':
    app.run_server(debug=True, port=8000)

文档:间隔 | Dash for Python 文档 |阴谋


0
投票

首先,从

Dash v1.19.0
开始,在同一回调中传递相同的组件+属性对作为输入和输出的情况被专门视为循环回调的例外。您可以通过查看拉取请求#1525获得更多详细信息。所以它实际上打破了循环。

其次,使用按钮的

n_clicks
进行同步似乎不是一个好主意。按钮的点击首先在客户端处理。每个都会增加
n_clicks
并因此发出回调。可能有很多。但输出中填充的是“最后发出的”回调(而不是最后收到的)的结果。在那一刻之前,每次新的点击都会导致带有客户端 n_clicks 值的回调,从而删除从服务器接收到的所有信息。举例说明:
import random

app.layout = html.Div([
    html.Div(id='output-container'),
    html.Button(id='clickme', children='Click me!', n_clicks=0)])

@app.callback(
    Output('output-container', 'children', allow_duplicate=True),
    Output('clickme', 'n_clicks', allow_duplicate=True),
    Input('clickme', 'n_clicks'),
    prevent_initial_call=True)
def click_me_click(clicks):
    time.sleep(random.randint(0, 1000) / 1000)  # Random delay within 1 sec    

    clicks += 1000
    print(clicks)
    return clicks, clicks

快速单击按钮,您可能会在
stdout

中得到一些类似的输出:

Clicks: 1001
Clicks: 1003  <<< shown in the output container
Clicks: 1002
Clicks: 2004  <<< increment of the last issued callback
Clicks: 2006  <<< shown in the output container
Clicks: 2005  <<< dropped

第三,我假设 
time.sleep(1)

表示您希望按顺序执行并避免任何竞争条件的耗时计算。

可能的解决方案是将一次触发的组件放入容器中。并在主循环回调中使用新初始化的触发器组件重写容器的内容。

触发:

from dash import dcc RUN_ONCE = 1 # Needs to be positive. # Zero value will lead to default 1000ms = 1sec delay of dcc.Interval DELAY_MS = 1 # 1 ms, no delay def trigger(): return dcc.Interval( id="trigger", interval=DELAY_MS, max_intervals=RUN_ONCE)

布局,单独存储调用次数:

app.layout = html.Div([ html.Button(children='Run Process', id='run-button', n_clicks=0), html.Div(id='trigger-container', children=trigger()), dcc.Store(id='counter', data=0), html.Div(id='output-container')])

循环回调:

from dash import State from dash.exceptions import PreventUpdate import random @app.callback( Output('output-container', 'children'), Output('trigger-container', 'children'), Output('counter', 'data'), Input('trigger', 'n_intervals'), State('counter', 'data')) def run_process(intervals_passed, count): if not intervals_passed: # Skip the initial callback after layout is ready raise PreventUpdate if count >= 10: raise PreventUpdate time.sleep(random.randint(0, 1000) / 1000) count += 1 message = f"Call count: {count}" print(message) return ( message, trigger(), count )

© www.soinside.com 2019 - 2024. All rights reserved.