前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Python+Dash快速web应用开发:回调交互篇(中)

Python+Dash快速web应用开发:回调交互篇(中)

作者头像
Python大数据分析
发布2022-04-03 09:53:29
2K0
发布2022-04-03 09:53:29
举报

❝本文示例代码已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes ❞

1 简介

这是我的系列教程「Python+Dash快速web应用开发」的第四期,在上一期的文章中,我们进入了Dash核心内容——callback,get到如何在不编写js代码的情况下,轻松实现前后端异步通信,为创造任意交互方式的Dash应用打下基础。

而在今天的文章中,我将带大家学习有关Dash「回调」的一些非常实用,且不算复杂的额外特性,让你更加熟悉Dash的回调交互~

图1

2 Dash中的回调实用小特性

2.1 灵活使用debug模式

开发阶段,在Dash中使用run_server()启动我们的应用时,可以添加参数debug=True来切换为「debug」模式,在这种模式下,我们可以获得以下辅助功能:

  • 「热重载」

热重载指的是,我们在编写完一个Dash的完整应用并在debug模式下启动之后,在保持应用运行的情况下,修改源代码并保存之后,浏览器中运行的Dash实例会自动重启刷新,就像下面的例子一样:

❝app1.py ❞

代码语言:javascript
复制
import dash
import dash_html_components as html

app = dash.Dash(__name__)

app.layout = html.Div(
    html.H1('我是热重载之前!')
)

if __name__ == '__main__':
    app.run_server(debug=True)

图2

可以看到,debug模式下,我们对源代码做出的修改在保存之后,都会受到Dash的监听,从而做出反馈(注意一定要在作出修改的代码完整之后再保存,否则代码写到一半就保存会引起语法错误等中断当前Dash实例)。

  • 「对回调结构进行可视化」

你可能已经注意到,在开启debug模式之后,我们浏览器中的Dash应用右下角出现的蓝色logo,点击打开折叠,可以看到几个按钮:

图3

其中第一个「Callbacks」非常有意思,它可以帮助我们对当前Dash应用中的回调关系进行可视化,譬如下面的例子:

❝app2.py ❞

代码语言:javascript
复制
import dash
import dash_bootstrap_components as dbc
import dash_html_components as html
from dash.dependencies import Input, Output

app = dash.Dash(
    __name__,
    external_stylesheets=['css/bootstrap.min.css']
)

app.layout = html.Div(
    dbc.Container(
        [
            html.Br(),
            html.Br(),
            html.Br(),
            dbc.Row(
                [
                    dbc.Col(
                        dbc.Input(id='input1'),
                        width=4
                    ),
                    dbc.Col(
                        dbc.Label(id='output1'),
                        width=4
                    )
                ]
            ),
            dbc.Row(
                [
                    dbc.Col(
                        dbc.Input(id='input2'),
                        width=4
                    ),
                    dbc.Col(
                        dbc.Label(id='output2'),
                        width=4
                    )
                ]
            )
        ]
    )
)

@app.callback(
    Output('output1', 'children'),
    Input('input1', 'value')
)
def callback1(value):

    if value:
        return int(value) ** 2


@app.callback(
    Output('output2', 'children'),
    Input('input2', 'value')
)
def callback2(value):

    if value:
        return int(value) ** 0.5

if __name__ == "__main__":
    app.run_server(debug=True)

图4

可以看到,我们打开「Callbacks」之后,可以看到每个回调的输入输出、通信延迟等信息,可以帮助我们更有条理的组织各个回调。

  • 「展示运行错误信息」

既然主要功能是debug,自然是可以帮助我们在程序出现错误时打印具体的错误信息,我们在前面app2.py例子的基础上,故意制造一些错误(此处代码粘贴有误,请查看评论区说明):

❝app3.py ❞

代码语言:javascript
复制
import dash
import dash_bootstrap_components as dbc
import dash_core_components as dcc
import dash_html_components as html

app = dash.Dash(
    __name__,
    external_stylesheets=['css/bootstrap.min.css']
)

app.layout = html.Div(
    [
        # fluid默认为False
        dbc.Container(
            [
                dcc.Dropdown(),
                '测试',
                dcc.Dropdown()
            ]
        ),

        html.Hr(), # 水平分割线

        # fluid设置为True
        dbc.Container(
            [
                dcc.Dropdown(),
                '测试',
                dcc.Dropdown()
            ],
            fluid=True
        )
    ]
)

if __name__ == "__main__":
    app.run_server()

图5

可以看到,我们故意制造出的两种错误:「不处理Input()默认的缺失值value」「Output()传入不存在的id」,都在浏览器中得到输出,并且可自由查看错误信息,这对我们开发过程帮助很大。

2.2 阻止应用的初始回调

在前面的app3例子中,我们故意制造出的错误之一是「不处理Input()默认的缺失值value」,这里的错误展开来说是因为Input()部件value属性的默认值是None,使得刚载入应用还未输入值时引发了回调中计算部分的逻辑错误。

类似这样的情况很多,可以通过给部件相应属性设置默认值或者在回调中写条件判断等方式处理,就像app2中那样,但如果这样的部件比较多,一个一个逐一处理还是比较繁琐,而Dash中提供了「阻止初始回调」的特性,只需要在app.callback装饰器中设置参数prevent_initial_call=True即可:

❝app4.py ❞

代码语言:javascript
复制
import dash
import dash_bootstrap_components as dbc
import dash_html_components as html
from dash.dependencies import Input, Output

app = dash.Dash(
    __name__,
    external_stylesheets=['css/bootstrap.min.css']
)

app.layout = html.Div(
    dbc.Container(
        [
            html.Br(),
            html.Br(),
            html.Br(),
            dbc.Row(
                [
                    dbc.Col(
                        dbc.Input(id='input1'),
                        width=4
                    ),
                    dbc.Col(
                        dbc.Label(id='output1'),
                        width=4
                    )
                ]
            )
        ]
    )
)


@app.callback(
    Output('output1', 'children'),
    Input('input1', 'value'),
    prevent_initial_call=True
)
def callback1(value):

    return int(value) ** 2

if __name__ == "__main__":
    app.run_server(debug=True)

图6

可以看到,设置完参数后,Dash应用被访问时,不会自动执行首次回调,非常的方便。

2.3 忽略回调匹配错误

在前面我们还制造出了「Output()传入不存在的id」这种错误,也就是回调函数查找输入输出等关系时,出现匹配失败的情况。

但在很多时候,我们需要在发生某些交互回调时,才创建返回一些具有指定「id」的部件,这时如果程序中提前写好了针对这些初始化时「不存在」的部件的回调,就会触发前面的错误。

Dash中提供了解决此类问题的方法,在创建app实例时添加参数suppress_callback_exceptions=True即可:

❝app5.py ❞

代码语言:javascript
复制
import dash
import dash_bootstrap_components as dbc
import dash_html_components as html
import dash_core_components as dcc
from dash.dependencies import Input, Output

app = dash.Dash(
    __name__,
    external_stylesheets=['css/bootstrap.min.css'],
    # suppress_callback_exceptions=True
)

app.layout = html.Div(
    dbc.Container(
        [
            dbc.Row(
                [
                    dbc.Col(
                        dbc.Input(id='input_num')
                    ),
                    dbc.Col(id='output_item')
                ]
            ),
            dbc.Row(
                dbc.Col(
                    dbc.Label(id='output_desc')
                )
            )
        ]
    )
)


@app.callback(
    Output('output_item', 'children'),
    Input('input_num', 'value'),
    prevent_initial_call=True
)
def callback1(value):
    return dcc.Dropdown(
        id='output_dropdown',
        options=[
            {'label': i, 'value': i}
            for i in range(int(value))
        ]
    )


@app.callback(
    Output('output_desc', 'children'),
    Input('output_dropdown', 'options'),
    prevent_initial_call=True
)
def callback2(options):
    return '生成的Dropdown部件共有{}个选项'.format(options.__len__())

if __name__ == "__main__":
    app.run_server(debug=True)

图7

可以看到,参数添加后,Dash会自动忽略类似的回调匹配错误,非常的实用,这个知识点我们会在以后的「前后端分离」篇中频繁地使用到,所以一定要记住它。

3 编写一个贷款计算器

get完今天所学的知识点后,我们通过实际的例子,来巩固上一期及这一期的内容,帮助大家对Dash中的回调基础知识有更好的理解。

今天我们要编写的例子,是贷款计算器,要编写出一个实际的贷款计算器,我们需要组织以下用户输入内容:

  • 「贷款总金额」
  • 「还款月份数量」
  • 「年利率」
  • 「还款方式」

其中还款方式主要有「等额本息」「等额本金」两种,我们利用之前介绍过的dash-bootstrap-components来搭建页面,其中「贷款金额」「还款月份数量」以及「年利率」我们都使用Input()部件来实现,并利用参数type="number"来约束其类型为数值。

「还款方式」是二选一,所以我们使用部件RadioItems()来实现,最后设置计算按钮,配合以前介绍过的State()n_clicks来交互执行计算,并以plotly.express折线图的形式呈现计算结果(这部分我们将在之后的「嵌入可视化」中详细介绍),最终得到的效果如下:

图8

代码如下:

❝app6.py ❞

代码语言:javascript
复制
import dash
import dash_html_components as html
import plotly.express as px
import dash_core_components as dcc
import dash_bootstrap_components as dbc
from dash.dependencies import Output, Input, State
import time

app = dash.Dash(
    __name__,
    external_stylesheets=['css/bootstrap.min.css'],
    suppress_callback_exceptions=True
)

app.layout = html.Div(
    dbc.Container(
        [
            html.Br(),
            html.Br(),
            html.Br(),
            html.Br(),
            dbc.Row(
                dbc.Col(
                    dbc.InputGroup(
                        [
                            dbc.InputGroupAddon("贷款金额", addon_type="prepend"),
                            dbc.Input(
                                id='loan_amount',
                                placeholder='请输入贷款总金额',
                                type="number",
                                value=100
                            ),
                            dbc.InputGroupAddon("万元", addon_type="append"),
                        ],
                    ),
                    width={'size': 6, 'offset': 3}
                )
            ),
            html.Br(),
            dbc.Row(
                dbc.Col(
                    dbc.InputGroup(
                        [
                            dbc.InputGroupAddon("计划还款月数", addon_type="prepend"),
                            dbc.Input(
                                id='repay_month_amount',
                                placeholder='请输入计划还款月数',
                                type="number",
                                value=24,
                                min=1,
                                step=1
                            ),
                            dbc.InputGroupAddon("个月", addon_type="append"),
                        ],
                    ),
                    width={'size': 6, 'offset': 3}
                )
            ),
            html.Br(),
            dbc.Row(
                dbc.Col(
                    dbc.InputGroup(
                        [
                            dbc.InputGroupAddon("年利率", addon_type="prepend"),
                            dbc.Input(
                                id='interest_rate',
                                placeholder='请输入年利率',
                                type="number",
                                value=5,
                                min=0,
                                step=0.001
                            ),
                            dbc.InputGroupAddon("%", addon_type="append"),
                        ],
                    ),
                    width={'size': 6, 'offset': 3}
                )
            ),
            html.Br(),
            dbc.Row(
                dbc.Col(
                    dbc.RadioItems(
                        id="repay_method",
                        options=[
                            {"label": "等额本息", "value": "等额本息"},
                            {"label": "等额本金", "value": "等额本金"}
                        ],
                        value='等额本息'
                    ),
                    width={'size': 6, 'offset': 3}
                ),
            ),
            html.Br(),
            dbc.Row(
                dbc.Col(
                    dbc.Button('开始计算', id='start', n_clicks=0, color='light'),
                    width={'size': 6, 'offset': 3}
                ),
            ),
            html.Br(),
            dbc.Row(
                dbc.Col(
                    dcc.Loading(dcc.Graph(id='repay_timeline')),
                    width={'size': 6, 'offset': 3}
                ),
            ),
        ],
        fluid=True
    )
)


def make_line_graph(loan_amount,
                    repay_month_amount,
                    interest_rate,
                    repay_method):
    interest_rate /= 100
    loan_amount *= 10000

    month_interest_rate = interest_rate / 12

    if repay_method == '等额本息':

        month_repay = loan_amount * month_interest_rate * pow((1 + month_interest_rate), repay_month_amount) / \
                      (pow((1 + month_interest_rate), repay_month_amount) - 1)

        month_repay = round(month_repay, 2)

        month_repay = [month_repay] * repay_month_amount

    else:

        d = loan_amount / repay_month_amount
        month_repay = [round(d + (loan_amount - d * (month - 1)) * month_interest_rate, 3)
                       for month in range(1, repay_month_amount + 1)]

    fig = px.line(x=[f'第{i}月' for i in range(1, repay_month_amount + 1)],
                  y=month_repay,
                  title='每月还款金额变化曲线(总支出:{}元)'.format(round(sum(month_repay), 2)),
                  template='plotly_white')

    return fig

@app.callback(
    Output('repay_timeline', 'figure'),
    Input('start', 'n_clicks'),
    [State('loan_amount', 'value'),
     State('repay_month_amount', 'value'),
     State('interest_rate', 'value'),
     State('repay_method', 'value')],
    prevent_initial_call=True
)
def refresh_repay_timeline(n_clicks, loan_amount, repay_month_amount, interest_rate, repay_method):
    time.sleep(0.2) # 增加应用的动态效果

    return make_line_graph(loan_amount, repay_month_amount, interest_rate, repay_method)


if __name__ == '__main__':
    app.run_server(debug=True)

以上就是本文全部内容,下一期中将为大家介绍Dash中更加巧妙的回调技巧,敬请期待。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-01-30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Python大数据分析 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1 简介
  • 2 Dash中的回调实用小特性
    • 2.1 灵活使用debug模式
      • 2.2 阻止应用的初始回调
        • 2.3 忽略回调匹配错误
        • 3 编写一个贷款计算器
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档