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

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

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

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

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

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

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

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

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)
相关文章
|
7天前
|
前端开发 API UED
Python后端与前端交互新纪元:AJAX、Fetch API联手,打造极致用户体验!
Python后端与前端交互新纪元:AJAX、Fetch API联手,打造极致用户体验!
32 2
|
7天前
|
前端开发 JavaScript 安全
深入理解Python Web开发中的前后端分离与WebSocket实时通信技术
在现代Web开发中,前后端分离已成为主流架构,通过解耦前端(用户界面)与后端(服务逻辑),提升了开发效率和团队协作。前端使用Vue.js、React等框架与后端通过HTTP/HTTPS通信,而WebSocket则实现了低延迟的全双工实时通信。本文结合Python框架如Flask和Django,探讨了前后端分离与WebSocket的最佳实践,包括明确接口规范、安全性考虑、性能优化及错误处理等方面,助力构建高效、实时且安全的Web应用。
22 2
|
7天前
|
前端开发 Python
前后端分离的进化:Python Web项目中的WebSocket实时通信解决方案
在现代Web开发领域,前后端分离已成为一种主流架构模式,它促进了开发效率、提升了应用的可维护性和可扩展性。随着实时数据交互需求的日益增长,WebSocket作为一种在单个长连接上进行全双工通讯的协议,成为了实现前后端实时通信的理想选择。在Python Web项目中,结合Flask框架与Flask-SocketIO库,我们可以轻松实现WebSocket的实时通信功能。
21 2
|
8天前
|
JavaScript 前端开发 UED
WebSocket在Python Web开发中的革新应用:解锁实时通信的新可能
在快速发展的Web应用领域中,实时通信已成为许多现代应用不可或缺的功能。传统的HTTP请求/响应模式在处理实时数据时显得力不从心,而WebSocket技术的出现,为Python Web开发带来了革命性的变化,它允许服务器与客户端之间建立持久的连接,从而实现了数据的即时传输与交换。本文将通过问题解答的形式,深入探讨WebSocket在Python Web开发中的革新应用及其实现方法。
22 3
|
7天前
|
数据库 开发者 Python
实战指南:用Python协程与异步函数优化高性能Web应用
在快速发展的Web开发领域,高性能与高效响应是衡量应用质量的重要标准。随着Python在Web开发中的广泛应用,如何利用Python的协程(Coroutine)与异步函数(Async Functions)特性来优化Web应用的性能,成为了许多开发者关注的焦点。本文将从实战角度出发,通过具体案例展示如何运用这些技术来提升Web应用的响应速度和吞吐量。
12 1
|
5天前
|
数据挖掘 索引 Python
Python数据挖掘编程基础3
字典在数学上是一个映射,类似列表但使用自定义键而非数字索引,键在整个字典中必须唯一。可以通过直接赋值、`dict`函数或`dict.fromkeys`创建字典,并通过键访问元素。集合是一种不重复且无序的数据结构,可通过花括号或`set`函数创建,支持并集、交集、差集和对称差集等运算。
14 9
|
1天前
|
存储 数据处理 开发者
深入浅出:Python编程基础与实战技巧
【9月更文挑战第32天】本文将引导读者从零开始,掌握Python编程语言的核心概念,并通过实际代码示例深入理解。我们将逐步探索变量、数据结构、控制流、函数、类和异常处理等基本知识,并结合实用案例,如数据处理、文件操作和网络请求,提升编程技能。无论您是初学者还是有一定经验的开发者,这篇文章都能帮助您巩固基础,拓展视野。
|
1天前
|
数据采集 机器学习/深度学习 人工智能
Python编程之旅:从基础到精通
【9月更文挑战第32天】本文将带你进入Python的世界,从基础语法到高级特性,再到实战项目,让你全面掌握Python编程技能。无论你是初学者还是有一定基础的开发者,都能在这篇文章中找到适合自己的学习路径和方法。让我们一起踏上Python编程之旅,开启一段充满挑战和乐趣的学习历程吧!
|
4天前
|
存储 开发者 Python
探索Python编程的奥秘
【9月更文挑战第29天】本文将带你走进Python的世界,通过深入浅出的方式,解析Python编程的基本概念和核心特性。我们将一起探讨变量、数据类型、控制结构、函数等基础知识,并通过实际代码示例,让你更好地理解和掌握Python编程。无论你是编程新手,还是有一定基础的开发者,都能在这篇文章中找到新的启示和收获。让我们一起探索Python编程的奥秘,开启编程之旅吧!
|
5天前
|
Python
Python编程的循环结构小示例(二)
Python编程的循环结构小示例(二)
下一篇
无影云桌面