在Dash中更靈活地編寫回撥函式

費弗裡發表於2023-11-15

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

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

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

在Dash中更靈活地編寫回撥函式

閱讀本文大約需要6分鐘

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

在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}'
    ]
在Dash中更靈活地編寫回撥函式

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

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

相關文章