在Plotly Dash中通过Upload组件在函数间传递Pandas DataFrame
在Plotly Dash中上传Excel并共享Pandas DataFrame的正确姿势
问题背景
在Plotly Dash中处理Excel工作簿时,遇到了无法将上传生成的Pandas DataFrame传递给其他函数的问题:
- 使用全局变量时触发
NameError: name 'df' is not defined; - 尝试函数间传递但不知道如何在依赖组件输入的
parse_contents中正确处理。
错误原因
- 全局变量的致命问题:
update_output函数在用户未上传文件时就尝试打印df,此时全局df尚未定义,直接抛出错误。而且全局变量在多用户场景下会导致数据串用,完全不符合Dash的设计逻辑。 - 返回值类型错误:
parse_contents同时返回HTML组件和DataFrame,但Dash的children属性只能接受组件/字符串,混入DataFrame会引发潜在的渲染错误。
解决方案:使用dcc.Store存储数据
Dash内置的dcc.Store是官方推荐的跨回调共享数据的方案,它会把数据存在浏览器端(或服务器端,可配置),安全且符合响应式设计。具体步骤:
- 在布局中添加
dcc.Store组件保存DataFrame的JSON格式数据; - 修改上传回调,解析文件后将DataFrame转成JSON存入Store;
- 其他需要使用DataFrame的函数,通过回调从Store读取并转回DataFrame。
修改后的完整代码
import base64 import datetime import io import dash from dash.dependencies import Input, Output, State from dash import dcc, html, dash_table import pandas as pd external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css'] app = dash.Dash(__name__, external_stylesheets=external_stylesheets) app.layout = html.Div([ dcc.Upload( id='upload-data', children=html.Div([ 'Drag and Drop or ', html.A('Select Files') ]), style={ 'width': '100%', 'height': '60px', 'lineHeight': '60px', 'borderWidth': '1px', 'borderStyle': 'dashed', 'borderRadius': '5px', 'textAlign': 'center', 'margin': '10px' }, multiple=True ), html.Div(id='output-data-upload'), # 新增dcc.Store存储DataFrame的JSON数据 dcc.Store(id='stored-dataframe', data={}), # 示例:展示DataFrame的基本信息,验证数据传递 html.Div(id='df-info') ]) def parse_contents(contents, filename, date): content_type, content_string = contents.split(',') decoded = base64.b64decode(content_string) df = None try: if 'csv' in filename: df = pd.read_csv(io.StringIO(decoded.decode('utf-8'))) elif 'xls' in filename or 'xlsx' in filename: workbook_xl = pd.ExcelFile(io.BytesIO(decoded)) df = pd.read_excel(workbook_xl, sheet_name=0) except Exception as e: print(e) return html.Div(['文件处理出错']), {} # 返回UI组件和DataFrame的JSON格式(orient='split'保留行列结构) return html.Div([ html.H5(filename), html.H6(datetime.datetime.fromtimestamp(date)), dash_table.DataTable( df.to_dict('records'), [{'name': i, 'id': i} for i in df.columns] ), html.Hr(), html.Div('原始内容预览'), html.Pre(contents[0:200] + '...', style={ 'whiteSpace': 'pre-wrap', 'wordBreak': 'break-all' }) ]), df.to_json(date_format='iso', orient='split') @app.callback( [Output('output-data-upload', 'children'), Output('stored-dataframe', 'data')], Input('upload-data', 'contents'), State('upload-data', 'filename'), State('upload-data', 'last_modified') ) def update_output(list_of_contents, list_of_names, list_of_dates): if list_of_contents is not None: children = [] stored_data = {} # 处理多文件上传,这里取最后一个有效文件的DataFrame,可按需调整 for c, n, d in zip(list_of_contents, list_of_names, list_of_dates): component, df_json = parse_contents(c, n, d) children.append(component) if df_json: stored_data = df_json return children, stored_data return [], {} # 示例回调:从Store读取DataFrame并用于业务逻辑 @app.callback( Output('df-info', 'children'), Input('stored-dataframe', 'data') ) def use_stored_dataframe(stored_data): if not stored_data: return html.Div('请上传文件查看数据信息') # 将JSON转回Pandas DataFrame df = pd.read_json(stored_data, orient='split') # 这里可以调用任何需要df的自定义函数 return html.Div([ html.H4('数据概览'), html.P(f'总行数:{df.shape[0]},总列数:{df.shape[1]}'), html.P(f'列名:{", ".join(df.columns)}') ]) if __name__ == '__main__': app.run_server(debug=True)
关键改动说明
- 新增
dcc.Store:安全存储DataFrame的JSON数据,避免全局变量的问题; - 调整
parse_contents返回值:只返回合法的UI组件和JSON数据,不再混入DataFrame; - 多回调协作:上传回调负责解析数据并存储,业务回调从Store读取数据,完全符合Dash的响应式流程;
- 支持多文件上传:代码中处理了多文件场景,可根据需求调整为存储多个DataFrame。
内容的提问来源于stack exchange,提问作者John Conor
相关产品推荐
相关产品推荐

