(數據科學學習手札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,之後每次等我們輸入完單詞,主動去點擊按鈕從而增加其被點擊次數記錄時,回調函數纔會被觸發,這樣就方便了我們的很多複雜應用場景~


  以上就是本期的全部內容,歡迎在評論區與我進行討論~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章