如何设计多页Dash应用以避免重复回调错误?
问题描述
将单页Dash仪表板重构为多页应用,添加About页面后出现**重复回调(duplicate callback)**错误,具体现象:
- 首次启动应用时,根页面加载正常,功能符合预期;
- 刷新页面时触发重复回调错误;
- 导航至About页面后也会触发该错误。
根本原因
每次加载Home页面的layout函数时,都会调用utils.py中的make_random_title和make_figure函数,而这两个函数内部通过@app.callback重复向同一个Dash实例注册了相同输入输出组合的回调。Dash不允许同一组Input/Output绑定多个回调,因此在页面刷新或切换时触发错误。
解决方案
将组件生成逻辑与回调注册逻辑分离,确保回调仅注册一次,同时保留通过工具函数批量生成相似组件的能力。
修改后的代码
1. utils.py:拆分组件生成与回调注册
import plotly.graph_objects as go import numpy as np from dash import Dash, Patch, html, dcc from dash.dependencies import Input, Output def sine(xs: np.array) -> np.array: return 100 * np.sin(xs) # 仅生成图表组件,不包含回调注册 def make_figure() -> html.Div: fig = go.Figure() fig.add_trace(go.Scatter( x=None, y=None, name="Randomized Data" )) fig.update_layout(height=800, template="plotly_dark") return html.Div(children=[ dcc.Graph(figure=fig, id="plotly-figure") ]) # 仅生成标题组件,不包含回调注册 def make_random_title() -> html.Div: adjectives = [ "Affable", "Affluent", "Deprived", "Destitute", "Elaborate", "Evocative", "Frivolous", "Gritty", "Immense" ] nouns = [ "Crackdown", "Culprit", "Discrepancy", "Endorsement", "Enmity", "Gravity", "Immunity", "Juncture", "Legislation", "Offspring" ] return html.Div(children=[ f"{np.random.choice(adjectives)} {np.random.choice(nouns)}" ], id="random-title") # 统一注册所有回调,仅需执行一次 def register_callbacks(app: Dash): @app.callback( Output("plotly-figure", "figure"), Input("dcc-interval", "n_intervals") ) def update_figure(n_clicks: int) -> Patch: xs = np.linspace(0, 6*np.pi, 100) ys = sine(xs) * np.random.random(size=len(xs)) patch = Patch() patch["data"][0]["x"] = xs patch["data"][0]["y"] = ys return patch @app.callback( Output("random-title", "children"), Input("dcc-interval", "n_intervals") ) def update_title(n_intervals: int) -> html.H1: adjectives = [ "Affable", "Affluent", "Deprived", "Destitute", "Elaborate", "Evocative", "Frivolous", "Gritty", "Immense" ] nouns = [ "Crackdown", "Culprit", "Discrepancy", "Endorsement", "Enmity", "Gravity", "Immunity", "Juncture", "Legislation", "Offspring" ] return html.H1(f"{np.random.choice(adjectives)} {np.random.choice(nouns)}")
2. main.py:初始化时注册回调
import dash import dash_bootstrap_components as dbc from dash import Dash, html, dcc import utils # 导入utils模块 def main() -> Dash: app = Dash(__name__, use_pages=True, external_stylesheets=[dbc.themes.SLATE]) # 仅在app初始化时注册一次回调 utils.register_callbacks(app) app.layout = html.Div(children=[ dash.page_container, dcc.Interval(id="dcc-interval", n_intervals=0, interval=1000) ]) return app if __name__ == "__main__": app = main() app.run(debug=True)
3. pages/home.py:简化组件调用
import dash import utils dash.register_page(__name__, path="/") from dash import html def layout() -> html.Div: return html.Div(children=[ # 无需传入app实例,直接调用组件生成函数 utils.make_random_title(), utils.make_figure() ])
方案优势
- 回调仅在应用启动时注册一次,彻底避免重复注册问题;
- 组件生成逻辑仍保留工具函数的复用性,可批量生成相似组件;
- 回调与组件逻辑分离,代码结构更清晰,便于维护。
内容的提问来源于stack exchange,提问作者Yoda
相关产品推荐
相关产品推荐

