本文示例程式碼已上傳至我的
Github
倉庫https://github.com/CNFeffery/dash-master
大家好我是費老師,幾天前我釋出了由我開源維護的dash
通用網頁元件庫fac
的0.2.x
全新版本,為大家介紹了其具有的諸多實用特性功能,也吸引了很多對基於dash
的Python
全棧應用開發感興趣的朋友,為了方便更多對dash
應用開發不甚瞭解的朋友快速入門,今天的文章中,我將透過簡潔明瞭的內容帶大家快速掌握dash
應用開發的必備基礎知識?。
閱讀本文大約需要10分鐘
1 環境搭建
dash
應用作為Python
專案,建議大家從一開始就養成好習慣,使用虛擬環境來構建我們的dash
應用執行所需環境,以我最常用的conda
為例,終端執行下列命令,建立名為dash-app-dev
,Python
版本為3.8
的虛擬環境:
conda create -n dash-app-dev python=3.8 -y
啟用該環境:
conda activate dash-app-dev
在該環境下使用pip
安裝必要依賴(dash
+fac
開發套件,以及用於開發階段程式碼格式自動美化的autopep8
),這裡為了國內下載加速,使用了阿里雲映象:
pip install dash feffery-antd-components autopep8 -i https://mirrors.aliyun.com/pypi/simple/
至此我們就完成了標準dash
應用所需Python
虛擬環境的搭建工作了。
2 初始化專案
有了環境,接下來我們在自己熟悉的ide中建立專案進行初始化即可,以我最喜歡用的vscode為例(記得事先安裝微軟官方的Python
外掛),我們先在本地某個位置新建示例工程目錄hello-dash
,接著在vscode中將此目錄作為專案開啟:
在當前專案根目錄新建檔案app.py
,即為我們本文演示用簡單小應用的主檔案,開啟app.py
後,在vscode右下角選擇環境為我們先前建立的dash-app-dev
即可:
3 dash應用基礎結構
有了作為應用主檔案的app.py
之後,我們就可以開始編寫dash
應用程式碼了,一個dash
應用具有以下幾個基本構成部分:
3.1 相關包的匯入
首先我們需要在app.py
檔案開頭匯入本文示例應用所需的各個模組,具體如下:
import dash # dash應用核心
from dash import html # dash自帶的原生html元件庫
import feffery_antd_components as fac # fac通用元件庫
from dash.dependencies import Input, Output, State # 用於構建應用互動功能的不同角色
3.2 例項化Dash()物件
接下來我們需要進行Dash()
物件的例項化,其具有的其他功能引數我們在今後的文章中再分別作詳細介紹:
app = dash.Dash(__name__)
3.3 為dash應用定義初始元素
在已例項化的Dash()
物件app
的基礎上,我們需要為其layout
屬性進行賦值,作為我們的dash
應用被訪問時,初始化載入的頁面內容,layout
可賦值為單個元件或返回單個元件的函式,通常我們會直接將一個html.Div()
元件賦給它:
app.layout = html.Div()
在此基礎上,我們可以將賦給app.layout
的html.Div()
元件作為最外層的容器,其他應用初始化時需要載入的更多元素,我們可以透過向下巢狀的方式傳給html.Div()
的children
引數。
在dash
元件的世界中,一個元件只要允許接受children
引數,就可以為其巢狀傳入單個元件,或由多個元件構成的列表,因為children
引數也是對應元件的第一個位置引數,所以我們可以像下面這樣很方便的傳入一些其他元件,這裡以fac
中的警告提示元件為例,我們將dash
和fac
的版本資訊傳入其對應引數中:
app.layout = html.Div(
[
# 這裡以fac中的警告提示元件為例
# 文件地址:https://fac.feffery.tech/AntdAlert
fac.AntdAlert(
message='Hello Dash!',
description=f'當前應用dash版本:{dash.__version__} fac版本:{fac.__version__}',
showIcon=True
)
]
)
3.4 啟動應用
完成了上述過程後,我們先來啟動一下當前的應用,在app.py
末尾新增下列程式碼,其中debug=True
用於啟用開發除錯模式,這是我們在dash
應用開發階段的好幫手,可以幫我們實現熱過載、錯誤資訊提示等便捷功能:
if __name__ == '__main__':
app.run(debug=True)
接著在終端中切換到該專案根目錄,也就是app.py
所在的目錄,在啟用dash-app-dev
環境的前提下,執行命令python app.py
即可臨時啟動我們的dash
應用,應用預設執行在http://127.0.0.1:8050
中,我們按照提示在瀏覽器中訪問即可:
在瀏覽器中就可以看到我們的dash
應用當前的樣子了~
3.5 調整應用樣式
眼下雖然我們這個非常簡單的dash
應用跑起來了,但是樣子著實簡陋,在dash
應用中針對元件元素的樣式進行調整的方式有很多種,最直接的方式是透過對應元件的style
引數進行相關css
樣式屬性的設定,譬如我們可以為最外層的html.Div()
容器設定一定的內邊距:
因為我們開啟了debug=True
模式,因此在調整程式碼後,按下ctrl+s
儲存app.py
最新變動後,瀏覽器中正在訪問的dash
應用會自動化重新整理,非常方便,可以看到,此時我們的應用已經有了內邊距:
3.6 基於回撥函式實現互動功能
到目前為止,我們的示例應用還僅僅是在展示靜態內容,當我們需要為dash
應用新增互動功能時,就需要用到dash
中的核心概念——回撥函式了,在回撥函式眼中,每個具有唯一id
引數的元件的任意屬性,都可以被編排為回撥函式中的角色,我們書寫回撥函式的過程實際上就是在玩角色編排的遊戲,在dash
中有Input
、Output
和State
三種角色,下面我們來舉例說明它們各自的作用:
假如我們現在需要在頁面中放置一個按鈕,並在使用者每次點選按鈕後,在按鈕旁邊展示其累計被點選的次數資訊,回撥函式就可以寫作(常規的回撥函式本質上是在用@app.callback()
對定義回撥邏輯的函式進行裝飾):
其中@app.callback()
中編排的內容翻譯成人話就是id
為button-demo
的元件的nClicks
屬性每次更新時,都會經過函式體內定義的邏輯將返回值更新到id
為button-demo-output
的元件的children
屬性,於是乎便實現了下面動圖展示的效果:
同時向多個Output
角色進行輸出更新也是可以的,譬如我們每次點選按鈕時不僅更新按鈕一側的資訊,還順便彈出訊息提示,就可以將程式碼修改為:
互動效果如下:
美中不足的是我們剛訪問應用,並沒有進行按鈕點選時,回撥函式自動就先執行了一遍,這是因為dash
應用預設會在應用初始化時對所有的回撥函式都自動執行一遍,不管其所編排的Input
角色是否更新,如果你不希望這種機制發生,那麼在@app.callback()
中設定引數prevent_initial_call=True
即可:
可以看到,這時初始訪問應用就不會有相關資訊自動被刷出:
透過上面的簡單例子,我們已經掌握了dash
回撥函式中Input
與Output
角色的作用,剩下的State
角色就比較特殊,不同於Input
那樣可以透過監聽目標元件的指定屬性變化從而觸發回撥函式執行,State
角色用來在回撥函式中提供輔助屬性值,相當於每次回撥函式因為某個Input
角色變化而被觸發時,會捎帶手把State
角色對應的屬性值一併攜帶進回撥函式中,起到輔助計算的作用。
舉個實際的例子,假如我們在按鈕一側新增一個輸入框,每次按鈕被點選時,都順便將輸入框中的已輸入內容傳遞進回撥進行使用,就可以寫作下面的方式:
有了額外State
角色的輔助,我們的應用互動效果就變成下面動圖所示:
至此,我們就get到dash
中回撥函式的基本寫法——即在@app.callback()
中按照Output
、Input
、State
的順序依次編排角色,且回撥函式輸入引數(引數名隨意)與已編排的Input
、State
角色順序一致即可~
3.7 更復雜的應用示例
掌握了上文所述的dash
應用最基礎概念後,下面我們就可以嘗試編寫更復雜的互動應用場景,譬如下面的簡單例子,我們在頁面中放置了若干表單輸入類元件,配合fac.AntdForm()
和fac.AntdFormItem()
進行表單的快捷構建,並透過回撥函式與下方的表格實現聯動篩選(以pandas
資料框為例),效果如下:
上面例子的完整程式碼如下,執行前請記得額外安裝pandas
:
# 相關包的匯入
import dash # dash應用核心
import pandas as pd
from dash import html # dash自帶的原生html元件庫
import feffery_antd_components as fac # fac通用元件庫
from dash.dependencies import Input, Output, State # 用於構建應用互動功能的不同角色
# 例項化Dash()物件
app = dash.Dash(__name__)
# 建立示例表格
demo_df = pd.DataFrame(
{
'欄位1': [f'類別{i}' for i in range(1, 11)],
'欄位2': [10*i for i in range(10)],
'欄位3': [(pd.Timestamp('2023-01-01') + pd.Timedelta(days=i)).strftime('%Y-%m-%d')
for i in range(10)]
}
)
# 為dash應用定義初始元素
app.layout = html.Div(
[
# 這裡以fac中的警告提示元件為例
# 文件地址:https://fac.feffery.tech/AntdAlert
fac.AntdAlert(
message='Hello Dash!',
description=f'當前應用dash版本:{dash.__version__} fac版本:{fac.__version__}',
showIcon=True
),
# 放置水平分割虛線
fac.AntdDivider(isDashed=True),
fac.AntdForm(
[
fac.AntdFormItem(
fac.AntdSelect(
id='field1-range',
options=[
{
'label': x,
'value': x
}
for x in demo_df['欄位1'].unique()
],
mode='multiple',
maxTagCount='responsive',
style={
'width': 200
}
),
label='欄位1'
),
fac.AntdFormItem(
fac.AntdSlider(
id='field2-range',
min=0,
max=100,
range=True,
defaultValue=[0, 100],
style={
'width': 150
}
),
label='欄位2'
),
fac.AntdFormItem(
fac.AntdDateRangePicker(
id='field3-range',
defaultPickerValue=demo_df['欄位3'].min(),
style={
'width': 200
}
),
label='欄位3'
),
fac.AntdButton(
'查詢',
id='execute-query',
icon=fac.AntdIcon(
icon='antd-search'
),
type='primary'
)
],
layout='inline',
style={
'marginBottom': 15
}
),
html.Div(id='table-result-container')
],
style={
# 這裡基於css中的padding引數,設定上下內邊距50畫素,左右內邊距100畫素
'padding': '50px 100px'
}
)
@app.callback(
Output('table-result-container', 'children'),
Input('execute-query', 'nClicks'),
[State('field1-range', 'value'),
State('field2-range', 'value'),
State('field3-range', 'value')]
)
def query_table(nClicks, field1_range, field2_range, field3_range):
demo_df_copy = demo_df.copy()
if field1_range:
demo_df_copy.query('欄位1 == @field1_range', inplace=True)
if field2_range:
demo_df_copy.query(f'{field2_range[0]} <= 欄位2 <= {field2_range[1]}',
inplace=True)
if field3_range:
demo_df_copy.query(f'"{field3_range[0]}" <= 欄位3 <= "{field3_range[1]}"',
inplace=True)
if not demo_df_copy.empty:
return fac.AntdTable(
columns=[
{
'title': column,
'dataIndex': column
}
for column in demo_df_copy.columns
],
data=demo_df_copy.to_dict('records'),
bordered=True
)
# 否則返回無匹配資料提示
return fac.AntdEmpty(
description='當前條件組合下無匹配資料'
)
if __name__ == '__main__':
app.run(debug=True)
以上就是本文的全部內容,更多有關dash
應用開發的前沿知識和技巧歡迎持續關注玩轉dash公眾號。