1. 程式人生 > >(資料科學學習手札104)Python+Dash快速web應用開發——回撥互動篇(上)

(資料科學學習手札104)Python+Dash快速web應用開發——回撥互動篇(上)

> 本文示例程式碼已上傳至我的`Github`倉庫[https://github.com/CNFeffery/DataScienceStudyNotes](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 ```Python 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`應用需要做什麼: - **確定輸入與輸出部件**   一個可互動的系統一定是有**輸入**與**輸出**的,我們開頭匯入的`Input`與`Output`物件,他們分別扮演著**輸入者**與**輸出者**兩種角色,其各自的第一個引數`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()`。 ```Python # 對應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 ```Python 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 ```Python 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 ```Python 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,之後每次等我們輸入完單詞,主動去點選按鈕從而增加其被點選次數記錄時,回撥函式才會被觸發,這樣就方便了我們的很多複雜應用場景~ ---   以上就是本期的全部內容,歡迎在評論區與我進