本文示例程式碼已上傳至我的
Github
倉庫https://github.com/CNFeffery/DataScienceStudyNotes
1 簡介
這是我的系列教程Python+Dash快速web應用開發的第三期,在前兩期的教程中,我們圍繞什麼是Dash
,以及如何配合方便好用的第三方擴充dash-bootstrap-components
來為我們的Dash
應用設計佈局展開了非常詳細的介紹。
而Dash
最吸引我的地方在於其高度封裝了react.js
,使得我們無需編寫js
語句,純Python
程式設計就可以實現瀏覽器前端與後端計算之間常規的非同步通訊,從而創造出功能強大的互動式web
應用。
從今天的文章開始,我就將開始帶大家走進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
的互動效果:
下面我們來分解上面的程式碼,梳理一下要構造一個具有實際互動功能的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()
。
# 對應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()
這裡我們的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()
可以看到不管是多個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()
可以看到,裝飾器中按照Output()
、Input()
、State()
的順序傳入各個物件後,我們的Button()
部件的n_clicks
引數記錄了對應的按鈕被點選了多少次,初始化我們設定其為0,之後每次等我們輸入完單詞,主動去點選按鈕從而增加其被點選次數記錄時,回撥函式才會被觸發,這樣就方便了我們的很多複雜應用場景~
以上就是本期的全部內容,歡迎在評論區與我進行討論~