• (数据科学学习手札104)Python+Dash快速web应用开发——回调交互篇(上)


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

    1 简介

       这是我的系列教程Python+Dash快速web应用开发的第三期,在前两期的教程中,我们围绕什么是Dash,以及如何配合方便好用的第三方拓展dash-bootstrap-components来为我们的Dash应用设计布局展开了非常详细的介绍。

      而Dash最吸引我的地方在于其高度封装了react.js,使得我们无需编写js语句,纯Python编程就可以实现浏览器前端与后端计算之间常规的异步通信,从而创造出功能强大的交互式web应用。

    图1

      从今天的文章开始,我就将开始带大家走进Dash的核心内容——回调

    2 Dash中的基础回调

    2.1 最基础的回调

      Dash中的回调callback)是以装饰器的形式,配合自编回调函数,实现前后端异步通信交互,这句话可能不太好理解,我们从一个简单的例子出发来认识Dash中的回调

    app1.py

    import dash
    import dash_html_components as html
    import dash_bootstrap_components as dbc
    from dash.dependencies import Input, Output
    
    app = dash.Dash(
        __name__,
        external_stylesheets=['css/bootstrap.min.css']
    )
    
    app.layout = html.Div(
        [
            html.Br(),
            html.Br(),
            html.Br(),
            dbc.Container(
                [
                    dbc.Row(
                        [
                            dbc.Col(dbc.Input(id='input-value',
                                              placeholder='请输入些东西'),
                                    width=12),
                            dbc.Col(dbc.Label(id='output-value'),
                                    width=12)
                        ]
                    )
                ]
            )
        ]
    )
    
    
    # 对应app实例的回调函数装饰器
    @app.callback(
        Output('output-value', 'children'),
        Input('input-value', 'value')
    )
    def input_to_output(input_value):
        '''
        简单的回调函数
        '''
        return input_value
    
    
    if __name__ == '__main__':
        app.run_server()
    

      先来看看app1的交互效果:

    图2

      下面我们来分解上面的代码,梳理一下要构造一个具有实际交互功能的Dash应用需要做什么:

    • 确定输入与输出部件

      一个可交互的系统一定是有输入输出的,我们开头导入的InputOutput对象,他们分别扮演着输入者输出者两种角色,其各自的第一个参数component_id用于联动前端部分定义的部件。

      我们在前面定义前端部件时,为dbc.Input对应的输入框设置了id='input-value',为dbc.Label对应的文字输出设置了id='output-value',让它们作为第一个参数可以被Input()Output()唯一识别出来。

    • 确定输入与输出内容

      在确定了输入者输出者之后,更重要的是为告诉Dash需要监听什么输入,响应什么输出,这就要用到第二个参数component_property

      它与对应的前端部件有关,譬如我们的dbc.Input()输入框,其被输入的内容都存在value属性中,而children属性是dbc.Label以及绝大多数html部件的第一个参数,这样我们就确定了输入输出内容。

    • 装饰回调函数

      app.callback()装饰器按照规定的先Output()Input()的顺序传入相应对象,而既然是装饰器,自然需要配合自定义回调函数使用。

      我们的input_to_output()就是对应的回调函数,其参数与装饰器中的Input()对应,而函数内部则用来定义计算处理过程。

      最后return的对象则对应Output()

    # 对应app实例的回调函数装饰器
    @app.callback(
        Output('output-value', 'children'),
        Input('input-value', 'value')
    )
    def input_to_output(input_value):
        '''
        简单的回调函数
        '''
        return input_value
    

      通过上面这样的结构,我们得以纯Python“寥寥数语”实现了交互功能,赋予我们编写任意功能Dash应用的能力。

    2.2 同时设置多个Input()与Output()

      在上一小节中我们介绍的是最基本的单输入 -> 单输出回调模式,很多时候我们需要更复杂的回调模式,譬如下面的例子:

    app2.py

    import dash
    import dash_html_components as html
    import dash_bootstrap_components as dbc
    from dash.dependencies import Input, Output
    
    app = dash.Dash(
        __name__,
        external_stylesheets=['css/bootstrap.min.css']
    )
    
    app.layout = html.Div(
        [
            html.Br(),
            html.Br(),
            html.Br(),
            dbc.Container(
                [
                    dbc.Row(
                        [
                            dbc.Col(dbc.Input(id='input-value1'), width=3),
                            dbc.Col(html.P('+'), width=1),
                            dbc.Col(dbc.Input(id='input-value2'), width=3),
                        ],
                        justify='start'
                    ),
                    html.Hr(),
                    dbc.Label(id='output-value')
                ]
            )
        ]
    )
    
    
    @app.callback(
        Output('output-value', 'children'),
        Input('input-value1', 'value'),
        Input('input-value2', 'value')
    )
    def input_to_output(input_value1, input_value2):
    
        try:
            return float(input_value1) + float(input_value2)
        except:
            return '请输入合法参数!'
    
    
    if __name__ == '__main__':
        app.run_server()
    
    图3

      这里我们的Input()对象不止一个,在Output()对象之后依次传入(也可以把所有Input()对象包在一个列表中传入),其顺序对应后面回调函数的参数顺序,从而实现了多个输入值的一一对应。

      同样的,Output()也可以有多个:

    app3.py

    import dash
    import dash_html_components as html
    import dash_bootstrap_components as dbc
    from dash.dependencies import Input, Output
    
    app = dash.Dash(
        __name__,
        external_stylesheets=['css/bootstrap.min.css']
    )
    
    app.layout = html.Div(
        [
            html.Br(),
            html.Br(),
            html.Br(),
            dbc.Container(
                [
                    dbc.Row(
                        [
                            dbc.Col(dbc.Input(id='input-lastname'), width=3),
                            dbc.Col(html.P('+'), width=1),
                            dbc.Col(dbc.Input(id='input-firstname'), width=3),
                        ],
                        justify='start'
                    ),
                    html.Hr(),
                    dbc.Label(id='output1'),
                    html.Br(),
                    dbc.Label(id='output2')
                ]
            )
        ]
    )
    
    
    @app.callback(
        [Output('output1', 'children'),
         Output('output2', 'children')],
        [Input('input-lastname', 'value'),
         Input('input-firstname', 'value')]
    )
    def input_to_output(lastname, firstname):
    
        try:
            return '完整姓名:' + lastname + firstname, f'姓名长度为{len(lastname+firstname)}'
        except:
            return '等待输入...', '等待输入...'
    
    
    if __name__ == '__main__':
        app.run_server()
    
    图4

      可以看到不管是多个Output()还是Input(),只需要嵌套在列表中即可。

    2.3 利用State()实现惰性交互

      很多情况下,如果我们的回调函数计算过程时间开销较大,那么像前面介绍的仅靠Input()Output()实现的前后端通信会很频繁,因为监听到的所有输入部件对应属性值只要略一改变,就会触发回调。

      为了解决这类问题,Dash中设计了State()对象,我们可以利用State()替换Input()来绑定对应的输入值,再将一些需要主动触发的譬如dbc.Button()按钮部件的属性n_clicks,作为Input()对象进行绑定。

      让我们通过下面的例子更好的理解它的作用:

    app4.py

    import dash
    import dash_html_components as html
    import dash_bootstrap_components as dbc
    from dash.dependencies import Input, Output, State
    
    app = dash.Dash(
        __name__,
        external_stylesheets=['css/bootstrap.min.css']
    )
    
    app.layout = html.Div(
        [
            html.Br(),
            html.Br(),
            html.Br(),
            dbc.Container(
                [
                    dbc.Row(
                        [
                            dbc.Col(dbc.Input(id='input-value'),
                                    width=4),
                            dbc.Col(dbc.Button('小写转大写',
                                               id='state-button',
                                               n_clicks=0),
                                    width=4),
                            dbc.Col(dbc.Label(id='output-value',
                                              style={'padding': '0',
                                                     'margin': '0',
                                                      'line-height': '38px'}),
                                    width=4)
                        ],
                        justify='start'
                    )
                ]
            )
        ]
    )
    
    
    @app.callback(
        Output('output-value', 'children'),
        Input('state-button', 'n_clicks'),
        State('input-value', 'value')
    
    )
    def input_to_output(n_clicks, value):
    
        if n_clicks:
            return value.upper()
    
    
    if __name__ == '__main__':
        app.run_server()
    
    图5

      可以看到,装饰器中按照Output()Input()State()的顺序传入各个对象后,我们的Button()部件的n_clicks参数记录了对应的按钮被点击了多少次,初始化我们设置其为0,之后每次等我们输入完单词,主动去点击按钮从而增加其被点击次数记录时,回调函数才会被触发,这样就方便了我们的很多复杂应用场景~


      以上就是本期的全部内容,欢迎在评论区与我进行讨论~

  • 相关阅读:
    功能测试-用例设计
    性能测试网址
    怎么调整磁盘分区的大小
    NIKKEI Programming Contest 2019-2 Task E. Non-triangular Triplets
    AtCoder Beginner Contest 131 Task F. Must Be Rectangular
    CF1244C The Football Season
    NIKKEI Programming Contest 2019-2 Task D. Shortest Path on a Line
    常见组合计数问题汇总
    【B2B】01-BFS
    【C++】A trick I learned:put boilerplate code into constructor of a struct
  • 原文地址:https://www.cnblogs.com/feffery/p/14313103.html
Copyright © 2020-2023  润新知