我正在尝试在破折号中创建回调循环。我的想法是有一个按钮,它会重新点击自身,直到我们点击 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。 知道如何修复以及发生了什么事吗?
也许你应该使用计时器
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 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
)