You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何设计多页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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 12:32:03