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

Plotly Dash应用链式回调未执行问题排查求助

问题:Plotly Dash链式回调未执行,无法按指定年份过滤数据

我正在学习Plotly Dash的链式回调功能,但所有回调都未执行。目标是让调用long_lasting_query()的第一个回调输出作为第二个回调的输入,最终展示按指定年份过滤后的数据。

原代码如下:

import argparse

import dash_bootstrap_components as dbc
import pandas as pd
from dash import Dash, Input, Output, State, dcc, html
from dash.dash_table import DataTable


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(
        description="Dash example",
        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
    )

    parser.add_argument(
        "-a",
        "--addr",
        type=str,
        metavar="arg",
        required=True,
        help="address to bind to (ip:port)",
    )
    parser.add_argument("--debug", action="store_true", help="run in debug mode")

    args = parser.parse_args()

    return args


def long_lasting_query() -> pd.DataFrame:
    df = pd.read_csv(
        "https://raw.githubusercontent.com/plotly/datasets/master/gapminder_unfiltered.csv"
    )

    return df


columns = ["country", "continent", "year", "lifeExp", "pop", "gdpPercap"]

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

app.layout = dbc.Container(
    [
        html.P(
            "Year: ",
            style={"display": "inline-block", "margin-right": "8px"},
        ),
        dcc.Input(id="year", type="number", min=0),
        html.Button("Submit", id="submit-val", n_clicks=0),
        DataTable(
            columns=[{"name": c, "id": c} for c in columns],
            id="tbl",
            page_size=20,
            sort_action="native",
            style_header={
                "textAlign": "center",
            },
        ),
    ]
)


@app.callback(
    Output("db_data", "value"),
    Input("submit-val", "n_clicks"),
    State("year", "value"),
    prevent_initial_call=True,
)
def load_data(n_clicks, value):
    df = long_lasting_query()
    df = df[df["year"] == value]
    if df.empty:
        return "{}"

    return df.to_json(orient="split")


@app.callback(
    Output("tbl", "data"),
    Input("db_data", "value"),
)
def update_table(value):
    df = pd.read_json(value, orient="split")
    if df.empty:
        return "{}"

    return df.to_dict("records")


def main() -> None:
    args = parse_args()
    host, port = args.addr.split(":")
    app.run(host=host, port=port, debug=args.debug)


if __name__ == "__main__":
    main()
问题原因与修复方案

核心错误

第一个回调的输出目标db_data在布局中不存在。Dash的回调必须关联布局内实际存在的组件,没有该组件时,第一个回调无法触发,后续链式回调自然也不会执行。

修复步骤

  1. 添加dcc.Store组件作为中间数据存储:这是Dash官方推荐的、用于回调间传递数据的专用组件,可在客户端存储JSON格式数据。
  2. 修正回调属性与空值逻辑:调整dcc.Store的属性引用,同时规范空数据返回格式,避免解析报错。

修复后的完整代码

import argparse

import dash_bootstrap_components as dbc
import pandas as pd
from dash import Dash, Input, Output, State, dcc, html
from dash.dash_table import DataTable


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(
        description="Dash example",
        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
    )

    parser.add_argument(
        "-a",
        "--addr",
        type=str,
        metavar="arg",
        required=True,
        help="address to bind to (ip:port)",
    )
    parser.add_argument("--debug", action="store_true", help="run in debug mode")

    args = parser.parse_args()

    return args


def long_lasting_query() -> pd.DataFrame:
    df = pd.read_csv(
        "https://raw.githubusercontent.com/plotly/datasets/master/gapminder_unfiltered.csv"
    )

    return df


columns = ["country", "continent", "year", "lifeExp", "pop", "gdpPercap"]

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

app.layout = dbc.Container(
    [
        html.P(
            "Year: ",
            style={"display": "inline-block", "margin-right": "8px"},
        ),
        dcc.Input(id="year", type="number", min=0),
        html.Button("Submit", id="submit-val", n_clicks=0),
        # 新增dcc.Store组件存储中间数据
        dcc.Store(id="db_data"),
        DataTable(
            columns=[{"name": c, "id": c} for c in columns],
            id="tbl",
            page_size=20,
            sort_action="native",
            style_header={
                "textAlign": "center",
            },
        ),
    ]
)


@app.callback(
    Output("db_data", "data"),  # 修正为dcc.Store的data属性
    Input("submit-val", "n_clicks"),
    State("year", "value"),
    prevent_initial_call=True,
)
def load_data(n_clicks, value):
    if not value:  # 处理年份未输入的情况
        return {}
    
    df = long_lasting_query()
    df = df[df["year"] == value]
    if df.empty:
        return {}  # 返回空字典而非字符串,避免解析报错

    return df.to_json(orient="split")


@app.callback(
    Output("tbl", "data"),
    Input("db_data", "data"),
    prevent_initial_call=True,  # 避免初始加载时触发无效回调
)
def update_table(value):
    if not value:
        return []  # 表格数据需为列表格式,空列表对应空表
    
    df = pd.read_json(value, orient="split")
    return df.to_dict("records")


def main() -> None:
    args = parse_args()
    host, port = args.addr.split(":")
    app.run(host=host, port=port, debug=args.debug)


if __name__ == "__main__":
    main()

关键修改点说明

  • 添加dcc.Store(id="db_data")到布局:填补了原代码中缺失的中间数据存储组件,让链式回调有了合法的传递节点。
  • 修正回调输出属性:dcc.Store的有效属性是data而非value,原代码的属性引用错误导致回调无法绑定。
  • 规范空值处理:
    • 年份未输入时直接返回空字典,避免无效数据查询。
    • 空数据时返回空字典/空列表,确保pd.read_json解析和表格渲染正常,原代码返回的字符串"{}"会触发解析错误。
  • 新增prevent_initial_call=True到第二个回调:避免页面加载时因db_data初始值为空而触发不必要的回调。

内容的提问来源于stack exchange,提问作者Constantine

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 04:14:52