本章描述如何使用回调函数来制作你的Dash应用程序:当输入组件的属性发生变化时,Dash会自动调用这些Python函数。
1.简单栗子
import dashimport dash_core_components as dccimport dash_html_components as htmlfrom dash.dependencies import Input, Outputexternal_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']app = dash.Dash(__name__, external_stylesheets=external_stylesheets)app.layout = html.Div([html.H6("Change the value in the text box to see callbacks in action!"),html.Div(["Input: ",dcc.Input(id='my-input', value='initial value', type='text')]),html.Br(),html.Div(id='my-output'),])@app.callback(Output(component_id='my-output', component_property='children'),Input(component_id='my-input', component_property='value'))def update_output_div(input_value):return 'Output: {}'.format(input_value)if __name__ == '__main__':app.run_server(debug=True)
让我们来分析一下这个例子:
app.callback装饰器通过声明,描述应用程序界面的“输入”与“输出”项;- 我们的应用程序的
**Input**和**output**只是特定组件的属性。在本例中,我们**Input**的**value**值是**ID**为 “**my-input**“。我们的**Output**是**ID**为“**my-output**”的组件的“**children**”属性。 - 每当输入属性发生变化时,回调装饰器包装的函数将被自动调用。Dash将输入属性的新值作为输入参数提供给函数,Dash使用函数返回的内容更新输出组件的属性。
- 不要混淆
dash.dependencies.Input和dash_core_components对象。前者只是在这些回调中使用,而后者是一个实际的组件。 - The
component_idandcomponent_property可以被省略 - Dash应用程序启动时,会自动使用输入组件的初始值,调用所有的回调函数,以填充输出组件的初始值。所以,不要在layout中设置 my-div组件的children特性,本例中,如果指定了
html.Div(id='my-div', children='Hello world')的内容,应用启动时会被覆盖。这种方式类似于Microsoft Excel编程:当单元格的内容发生变化时,依赖于该单元格的所有单元格的内容,都将自动更新。这称为 “反应式编程” (Reactive Programming) 。
@callback的详细用法:
- 通过编写这个装饰器,我们告诉Dash在“input”组件(文本框)的值发生变化时为我们调用这个函数,以便更新页面上“output”组件的子组件(HTML div)
- 可以使用任何的函数名称跟在@callback之后,但必须在回调函数中使用与定义中相同的名称(就和python语法一样)。约定是用名称来描述回调输出。
- 参数是有位置的。第一个(最底下那个)是
Input,后面跟任何state,顺序与装饰器中相同。- 当把Dash组件作为
@appback的输入或输出时,你必须使用与app.layout中Dash组件相同的id。回调装饰。callback后面紧跟着函数,不能有空行。- If you’re curious about what the decorator syntax means under the hood, you can read this StackOverflow answer and learn more about decorators by reading PEP 318 — Decorators for Functions and Methods.
2.滑块
import dashimport dash_core_components as dccimport dash_html_components as htmlfrom dash.dependencies import Input, Outputimport plotly.express as pximport pandas as pddf = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/gapminderDataFiveYear.csv')external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']app = dash.Dash(__name__, external_stylesheets=external_stylesheets)app.layout = html.Div([dcc.Graph(id='graph-with-slider'),dcc.Slider(id='year-slider',min=df['year'].min(),max=df['year'].max(),value=df['year'].min(),marks={str(year): str(year) for year in df['year'].unique()},step=None)])@app.callback(Output('graph-with-slider', 'figure'),Input('year-slider', 'value'))def update_figure(selected_year):filtered_df = df[df.year == selected_year]fig = px.scatter(filtered_df, x="gdpPercap", y="lifeExp",size="pop", color="continent", hover_name="country",log_x=True, size_max=55)fig.update_layout(transition_duration=500)return figif __name__ == '__main__':app.run_server(debug=True)

- 本例中,app的输入是
**Slider**的属性value,app的输出是**Graph**的属性figure。当Slider的value变化时,Dash用新值调用回调函数update_figure,该函数使用此新值过滤数据框,构造figure对象,并将其返回到Dash应用程序中,作为输出; - 使用关键字参数进行组件描述,很重要。通过Dash交互性,使用回调函数,可以动态地更新这些特性。如:更新组件的
**children**属性从而更新文本内容、更新**dcc.Graph**组件的**figure**属性从而更新数据、更新组件的**style**属性从而更新画布样式、更新**dcc.Dropdown**组件的**options**从而更新下拉菜单; - 将数据加载至内存并进行计算的代价很高,所以尽量在应用的全局范围内下载或查询数据,避免在回调函数里进行这类操作,确保用户访问或与应用交互时,数据(df)已经载入至内存。本例中df获取的数据是全局的,可以被回调函数读取;
- 回调函数不会修改原始数据,只是通过
**Pandas**的过滤器来筛选数据,并创建DataFrame的副本。这点非常重要:不要在回调函数范围之外更改变量。如果在全局状态下调整回调函数,某一用户的会话就可能影响下一用户的会话,特别是应用部署在多进程或多线程的环境时,这些修改可能会导致跨会话数据分享出现问题;
3.多端输入
在Dash中,任何“Input”都可以有多个“Output”组件。下面是一个简单的例子,它将五个输入(两个Dropdown组件的value属性、两个RadioItems组件和一个Slider组件)绑定到一个输出组件(Graph组件的figure属性)。回调函数的第二个参数,列表中列举了所有的五个输入项dash.dependencies.Input 。
import dashimport dash_core_components as dccimport dash_html_components as htmlfrom dash.dependencies import Input, Outputimport plotly.express as pximport pandas as pdexternal_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']app = dash.Dash(__name__, external_stylesheets=external_stylesheets)df = pd.read_csv('https://plotly.github.io/datasets/country_indicators.csv')available_indicators = df['Indicator Name'].unique()app.layout = html.Div([html.Div([html.Div([dcc.Dropdown(id='xaxis-column',options=[{'label': i, 'value': i} for i in available_indicators],value='Fertility rate, total (births per woman)'),dcc.RadioItems(id='xaxis-type',options=[{'label': i, 'value': i} for i in ['Linear', 'Log']],value='Linear',labelStyle={'display': 'inline-block'})],style={'width': '48%', 'display': 'inline-block'}),html.Div([dcc.Dropdown(id='yaxis-column',options=[{'label': i, 'value': i} for i in available_indicators],value='Life expectancy at birth, total (years)'),dcc.RadioItems(id='yaxis-type',options=[{'label': i, 'value': i} for i in ['Linear', 'Log']],value='Linear',labelStyle={'display': 'inline-block'})],style={'width': '48%', 'float': 'right', 'display': 'inline-block'})]),dcc.Graph(id='indicator-graphic'),dcc.Slider(id='year--slider',min=df['Year'].min(),max=df['Year'].max(),value=df['Year'].max(),marks={str(year): str(year) for year in df['Year'].unique()},step=None)])@app.callback(Output('indicator-graphic', 'figure'),Input('xaxis-column', 'value'),Input('yaxis-column', 'value'),Input('xaxis-type', 'value'),Input('yaxis-type', 'value'),Input('year--slider', 'value'))def update_graph(xaxis_column_name, yaxis_column_name,xaxis_type, yaxis_type,year_value):dff = df[df['Year'] == year_value]fig = px.scatter(x=dff[dff['Indicator Name'] == xaxis_column_name]['Value'],y=dff[dff['Indicator Name'] == yaxis_column_name]['Value'],hover_name=dff[dff['Indicator Name'] == yaxis_column_name]['Country Name'])fig.update_layout(margin={'l': 40, 'b': 40, 't': 10, 'r': 0}, hovermode='closest')fig.update_xaxes(title=xaxis_column_name,type='linear' if xaxis_type == 'Linear' else 'log')fig.update_yaxes(title=yaxis_column_name,type='linear' if yaxis_type == 'Linear' else 'log')return figif __name__ == '__main__':app.run_server(debug=True)

- 在本例中,每当
Dropdown、Slider或RadioItems组件的value属性更改时,就会调用update_graph函数。 update_graph函数的输入参数是每个输入属性的新值或当前值,按指定的顺序排列。- 即使一次只更改一个输入(用户在给定时刻只能更改单个下拉列表的值),Dash也会收集所有指定输入属性的当前状态,并将它们传递到函数中。你的回调函数总是保证会被传递给应用程序的代表状态。
4.多端输出
import dashimport dash_core_components as dccimport dash_html_components as htmlfrom dash.dependencies import Input, Outputexternal_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']app = dash.Dash(__name__, external_stylesheets=external_stylesheets)app.layout = html.Div([dcc.Input(id='num-multi',type='number',value=5),html.Table([html.Tr([html.Td(['x', html.Sup(2)]), html.Td(id='square')]),html.Tr([html.Td(['x', html.Sup(3)]), html.Td(id='cube')]),html.Tr([html.Td([2, html.Sup('x')]), html.Td(id='twos')]),html.Tr([html.Td([3, html.Sup('x')]), html.Td(id='threes')]),html.Tr([html.Td(['x', html.Sup('x')]), html.Td(id='x^x')]),]),])@app.callback(Output('square', 'children'),Output('cube', 'children'),Output('twos', 'children'),Output('threes', 'children'),Output('x^x', 'children'),Input('num-multi', 'value'))def callback_a(x):return x**2, x**3, 2**x, 3**x, x**xif __name__ == '__main__':app.run_server(debug=True)
- 一个Dash回调函数只能更新一个输出属性。要想实现多重输出,需要编写多个函数;
- 具体方法:将需要更新的所有属性,作为列表添加到装饰器中,并从回调中返回多个输出项。如果两个输出依赖于相同的计算密集型中间结果,例如慢速数据库查询,推荐使用该方法;
- 组合输出并不总是一个好主意:1)如果输出依赖于某些但不是所有相同的输入,则将它们分开可以避免不必要的更新;2)如果它们具有相同的输入,但使用这些输入进行独立计算,则将回调分开,可以实现并行运行它们;
5.Chained Callbacks
一个回调函数的输出是另一个回调函数的输入。
# -*- coding: utf-8 -*-import dashimport dash_core_components as dccimport dash_html_components as htmlfrom dash.dependencies import Input, Outputexternal_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']app = dash.Dash(__name__, external_stylesheets=external_stylesheets)all_options = {'America': ['New York City', 'San Francisco', 'Cincinnati'],'Canada': [u'Montréal', 'Toronto', 'Ottawa']}app.layout = html.Div([dcc.RadioItems(id='countries-radio',options=[{'label': k, 'value': k} for k in all_options.keys()],value='America'),html.Hr(),dcc.RadioItems(id='cities-radio'),html.Hr(),html.Div(id='display-selected-values')])@app.callback(Output('cities-radio', 'options'),Input('countries-radio', 'value'))def set_cities_options(selected_country):return [{'label': i, 'value': i} for i in all_options[selected_country]]@app.callback(Output('cities-radio', 'value'),Input('cities-radio', 'options'))def set_cities_value(available_options):return available_options[0]['value']@app.callback(Output('display-selected-values', 'children'),Input('countries-radio', 'value'),Input('cities-radio', 'value'))def set_display_children(selected_country, selected_city):return u'{} is a city in {}'.format(selected_city, selected_country,)if __name__ == '__main__':app.run_server(debug=True)
- 链式回调:将输出和输入链接在一起,即一个回调函数的输出是另一个回调函数的输入;
- 此模式用于创建动态UI,其中一个输入组件更新下一个输入组件的可用选项;
- 第二个单选按钮RadioItems的选项,基于第一个回调函数传递的单选按钮RadioItems中选择的值;
- 第二个回调函数设置了options特性改变时的初始值:将自身设置为options数组中的第一个值;
- 最后的回调函数,显示了每个组件中的可选内容。如果更改了城市单选按钮RadioItems组件的value属性,则Dash将等待,直到value更新状态组件后,再调用最后的回调函数。
6.状态回调
# -*- coding: utf-8 -*-import dashimport dash_core_components as dccimport dash_html_components as htmlfrom dash.dependencies import Input, Output, Stateexternal_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']app = dash.Dash(__name__, external_stylesheets=external_stylesheets)app.layout = html.Div([dcc.Input(id='input-1-state', type='text', value='Montréal'),dcc.Input(id='input-2-state', type='text', value='Canada'),html.Button(id='submit-button-state', n_clicks=0, children='Submit'),html.Div(id='output-state')])@app.callback(Output('output-state', 'children'),Input('submit-button-state', 'n_clicks'),State('input-1-state', 'value'),State('input-2-state', 'value'))def update_output(n_clicks, input1, input2):return u'''The Button has been pressed {} times,Input 1 is "{}",and Input 2 is "{}"'''.format(n_clicks, input1, input2)if __name__ == '__main__':app.run_server(debug=True)
在这个例子中,更改输入框不会有返回值的改变,只有点击SUBMIT才会更新返回值。dcc.Input的值依然会传入到回调参数中,但是仅仅是作为State传入,当SUBMIT的控件触发后,才会把State的value传入到函数中。
7.小结
- Dash应用是基于下述简单但强大的原则进行构建的:通过响应式与函数式的Python回调函数,自定义声明式的UI;
- 声明式组件中的每个元素属性,都可以通过回调函数和属性子集进行更新,比如dcc.Dropdown的value特性,这样用户就可以在交互界面中进行编辑。
注释参考:https://www.jianshu.com/p/cb0dc98e00bc
官方文档:https://dash.plotly.com/basic-callbacks




