本文示例代碼已上傳至我的
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公衆號。