10分鐘極速入門dash應用開發

本文示例代碼已上傳至我的Github倉庫https://github.com/CNFeffery/dash-master

大家好我是費老師,幾天前我發佈了由我開源維護的dash通用網頁組件庫fac0.2.x全新版本,爲大家介紹了其具有的諸多實用特性功能,也吸引了很多對基於dashPython全棧應用開發感興趣的朋友,爲了方便更多對dash應用開發不甚瞭解的朋友快速入門,今天的文章中,我將通過簡潔明瞭的內容帶大家快速掌握dash應用開發的必備基礎知識😉。

閱讀本文大約需要10分鐘

1 環境搭建

dash應用作爲Python項目,建議大家從一開始就養成好習慣,使用虛擬環境來構建我們的dash應用運行所需環境,以我最常用的conda爲例,終端執行下列命令,創建名爲dash-app-devPython版本爲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.layouthtml.Div()組件作爲最外層的容器,其他應用初始化時需要加載的更多元素,我們可以通過向下嵌套的方式傳給html.Div()children參數。

dash組件的世界中,一個組件只要允許接受children參數,就可以爲其嵌套傳入單個組件,或由多個組件構成的列表,因爲children參數也是對應組件的第一個位置參數,所以我們可以像下面這樣很方便的傳入一些其他組件,這裏以fac中的警告提示組件爲例,我們將dashfac的版本信息傳入其對應參數中:

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中有InputOutputState三種角色,下面我們來舉例說明它們各自的作用:

假如我們現在需要在頁面中放置一個按鈕,並在用戶每次點擊按鈕後,在按鈕旁邊展示其累計被點擊的次數信息,回調函數就可以寫作(常規的回調函數本質上是在用@app.callback()對定義回調邏輯的函數進行裝飾):

其中@app.callback()中編排的內容翻譯成人話就是idbutton-demo的組件的nClicks屬性每次更新時,都會經過函數體內定義的邏輯將返回值更新到idbutton-demo-output的組件的children屬性,於是乎便實現了下面動圖展示的效果:

同時向多個Output角色進行輸出更新也是可以的,譬如我們每次點擊按鈕時不僅更新按鈕一側的信息,還順便彈出消息提示,就可以將代碼修改爲:

交互效果如下:

美中不足的是我們剛訪問應用,並沒有進行按鈕點擊時,回調函數自動就先執行了一遍,這是因爲dash應用默認會在應用初始化時對所有的回調函數都自動執行一遍,不管其所編排的Input角色是否更新,如果你不希望這種機制發生,那麼在@app.callback()中設置參數prevent_initial_call=True即可:

可以看到,這時初始訪問應用就不會有相關信息自動被刷出:

通過上面的簡單例子,我們已經掌握了dash回調函數中InputOutput角色的作用,剩下的State角色就比較特殊,不同於Input那樣可以通過監聽目標組件的指定屬性變化從而觸發回調函數執行,State角色用來在回調函數中提供輔助屬性值,相當於每次回調函數因爲某個Input角色變化而被觸發時,會捎帶手把State角色對應的屬性值一併攜帶進回調函數中,起到輔助計算的作用。

舉個實際的例子,假如我們在按鈕一側添加一個輸入框,每次按鈕被點擊時,都順便將輸入框中的已輸入內容傳遞進回調進行使用,就可以寫作下面的方式:

有了額外State角色的輔助,我們的應用交互效果就變成下面動圖所示:

至此,我們就get到dash中回調函數的基本寫法——即在@app.callback()中按照OutputInputState的順序依次編排角色,且回調函數輸入參數(參數名隨意)與已編排的InputState角色順序一致即可~

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公衆號。

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