在Dash中更靈活地編寫回調函數

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

大家好我是費老師,使用Dash開發過交互式應用的朋友,想必都不會對回調函數感到陌生,作爲Dash應用中實現各種交互邏輯的“萬金油”方式,不管是常規的@app.callback(),還是對應瀏覽器端回調app.clientside_callback()ClientsideFunction(),其中編排各種回調角色時,我們都是按照先Output,再Input,最後State的順序依次羅列的,且各個角色存在多個時,建議用[]將它們包裹住,以提升代碼可讀性。

但這並不是不可打破的鐵律,事實上,Dash還額外提供了多種多樣的回調角色編排方式,官方稱之爲Flexible Callback Signatures,從而解決單個回調函數中角色太多時代碼可讀性變差等問題,今天的文章中,我就將帶大家學習相關的實用知識,從而更清晰地進行Dash應用開發及維護😇。

閱讀本文大約需要6分鐘

爲了方便演示,我們構造下圖所示的簡單示例Dash應用(完整源碼見文章開頭地址):

如果要編排以兩個按鈕作爲示例Input角色,兩個輸入框作爲示例State角色,並向兩個文字組件中分別Output不同的參數值內容的回調函數,按照常規的寫法,對應的回調函數可以寫作下方形式:

@app.callback(
    [Output('demo-output1', 'children'),
     Output('demo-output2', 'children')],
    [Input('demo-button1', 'nClicks'),
     Input('demo-button2', 'nClicks')],
    [State('demo-input1', 'value'),
     State('demo-input2', 'value')],
    prevent_initial_call=True
)
def demo_callback(nClicks1, nClicks2, value1, value2):

    return [
        f'nClicks1: {nClicks1}, nClicks2: {nClicks2}',
        f'value1: {value1}, value2: {value2}'
    ]

下面我們以此爲基礎,分別介紹其他不同的寫法:

1 字典化角色編排

我們可以用字典來分別編排各類型的角色,其中具體可細分爲:

  • InputState字典化

當僅對回調函數的InputState角色進行字典化編排時,我們可以通過自定義的鍵值對,完成針對回調函數輸入參數的映射,改造後的示例回調函數如下:

@app.callback(
    [Output('demo-output1', 'children'),
     Output('demo-output2', 'children')],
    inputs=dict(
        nClicks1=Input('demo-button1', 'nClicks'),
        nClicks2=Input('demo-button2', 'nClicks')
    ),
    state=dict(
        value1=State('demo-input1', 'value'),
        value2=State('demo-input2', 'value')
    ),
    prevent_initial_call=True
)
def demo_callback(nClicks1, nClicks2, value1, value2):
    '''字典化角色編排:僅Input、State字典化'''

    return [
        f'nClicks1: {nClicks1}, nClicks2: {nClicks2}',
        f'value1: {value1}, value2: {value2}'
    ]
  • 全部角色字典化

如果我們將回調函數的Output也進行了字典化改造,那麼在回調函數中就需要返回對應鍵值對的字典(返回單個dash.no_update時不受限制),示例寫法如下:

@app.callback(
    output=dict(
        content1=Output('demo-output1', 'children'),
        content2=Output('demo-output2', 'children')
    ),
    inputs=dict(
        nClicks1=Input('demo-button1', 'nClicks'),
        nClicks2=Input('demo-button2', 'nClicks')
    ),
    state=dict(
        value1=State('demo-input1', 'value'),
        value2=State('demo-input2', 'value')
    ),
    prevent_initial_call=True
)
def demo_callback(nClicks1, nClicks2, value1, value2):
    '''字典化角色編排:全部角色字典化'''

    return dict(
        content1=f'nClicks1: {nClicks1}, nClicks2: {nClicks2}',
        content2=f'value1: {value1}, value2: {value2}'
    )

通過字典化角色的形式,我們可以爲每個角色自由起名字,建議是起跟功能相關的名字,如login_button_click,或登錄按鈕點擊這樣的中文鍵名,只要能幫助你更好地讀懂回調函數邏輯就可以😉。

2 嵌套式字典化角色編排

當我們在使用上文所介紹的字典化角色編排方式時,除了在字典中平鋪書寫相應角色外,還可以向下繼續進行字典嵌套,從而實現更自由的參數分組效果,相應的,對應輸入參數也會以字典的形式傳入內部的各鍵值對參數:

@app.callback(
    output=dict(
        content1=Output('demo-output1', 'children'),
        content2=Output('demo-output2', 'children')
    ),
    inputs=dict(
        nClicks1=Input('demo-button1', 'nClicks'),
        nClicks2=Input('demo-button2', 'nClicks')
    ),
    state=dict(
        input_values=dict(
            value1=State('demo-input1', 'value'),
            value2=State('demo-input2', 'value')
        )
    ),
    prevent_initial_call=True
)
def demo_callback(nClicks1, nClicks2, input_values):
    '''嵌套式字典化角色編排'''

    return dict(
        content1=f'nClicks1: {nClicks1}, nClicks2: {nClicks2}',
        content2='value1: {value1}, value2: {value2}'.format(**input_values)
    )

3 對需要返回若干dash.no_update的情況進行簡化

針對字典化角色編排Output的方式,當我們僅需要對部分輸出目標返回實際值,對其餘目標返回dash.no_update時,可以配合標準庫collections中的defaultdict以及dash回調的上下文簡化相關過程:

@app.callback(
    output=dict(
        content1=Output('demo-output1', 'children'),
        content2=Output('demo-output2', 'children')
    ),
    inputs=dict(
        nClicks1=Input('demo-button1', 'nClicks'),
        nClicks2=Input('demo-button2', 'nClicks')
    ),
    state=dict(
        value1=State('demo-input1', 'value'),
        value2=State('demo-input2', 'value')
    ),
    prevent_initial_call=True
)
def demo_callback(nClicks1, nClicks2, value1, value2):
    '''字典化Output配合defaultdict'''

    # 假設我們需要除了content1之外的其他角色默認輸出爲dash.no_update
    output = defaultdict(
        lambda: dash.no_update,
        dict(
            content1=f'nClicks1: {nClicks1}, nClicks2: {nClicks2}'
        )
    )

    return {
        key: output[key]
        # 通過上下文遍歷所有Output字典鍵名
        for key in dash.ctx.outputs_grouping.keys()
    }

其中構造defaultdict並設置默認值等過程,我也會在fac即將發佈的0.3.x版本中封裝爲一步到位的工具函數,畢竟這種場景在進階Dash應用的開發中還是很常用的,省得在常規方式中逐個寫dash.no_update或其他默認值。

除此之外,有關Flexible Callback Signatures還有一些其他的寫法,但是在我看來並沒有字典化寫法這麼實用,感興趣的朋友可以移步https://dash.plotly.com/flexible-callback-signatures瞭解更多。


以上就是本文的全部內容,更多有關dash應用開發的前沿知識和技巧歡迎持續關注玩轉dash公衆號。

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