Dash 2.9.0版本重磅新功能一覽

2023-03-17 18:00:44

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

  大家好我是費老師,就在昨晚,Dash框架釋出了其2.9.0版本更新,在一眾更新內容中,有兩條新特性在我看來尤為重要,可以大幅度提升我們開發Dash應用的效率,下面我就將帶大家一起了解它們的具體內容:

1 允許多個回撥函數重複Output

  在之前版本的Dash中,嚴格限制了不同的回撥函數不可以對相同的id.屬性目標進行輸出,以下面的範例應用為例:

import dash
from dash import html
import feffery_antd_components as fac
from dash.dependencies import Input, Output

app = dash.Dash(__name__)

app.layout = html.Div(
    [
        fac.AntdSpace(
            [
                fac.AntdButton(
                    '按鈕1',
                    id='button-demo1'
                ),
                fac.AntdButton(
                    '按鈕2',
                    id='button-demo2'
                )
            ]
        ),
        fac.AntdParagraph(
            id='output-demo'
        )
    ],
    style={
        'padding': '50px 100px'
    }
)


@app.callback(
    Output('output-demo', 'children'),
    Input('button-demo1', 'nClicks'),
    prevent_initial_call=True
)
def trigger1(nClicks):

    return f'按鈕1: {nClicks}'


@app.callback(
    Output('output-demo', 'children'),
    Input('button-demo2', 'nClicks'),
    prevent_initial_call=True
)
def trigger2(nClicks):

    return f'按鈕2: {nClicks}'


if __name__ == '__main__':
    app.run(debug=True)

  如果我們希望兩個AntdButton分別點選後,可以通過兩個不同的回撥函數對同一AntdPargraph的內容進行輸出,在之前的版本中預設會報下圖所示的Duplicate callback outputs錯誤:

  在之前的版本中遇到這種情況解決方式也有很多,常用的如將多個回撥函數整合為一個並在回撥函數中,再基於dash.ctx.triggered_id判斷每次回撥函數究竟是由哪個Input觸發的,這在較複雜回撥功能的編寫中就不太方便了。

  而從Dash 2.9.0版本開始,為Output()引入了bool型新引數allow_duplicate,預設為False,當設定為True後,當前Output便可以允許通過多個回撥函數共同輸出,將上面的例子回撥部分進行改造,對後續重複的Output設定allow_duplicate=True

@app.callback(
    Output('output-demo', 'children', allow_duplicate=True),
    Input('button-demo2', 'nClicks'),
    prevent_initial_call=True
)
def trigger2(nClicks):

    return f'按鈕2: {nClicks}'

  就可以不受限制啦~

  當然,雖然有了這個新特性幫助我們解除了不少限制,但是我的建議還是不要濫用,它不一定可以使得我們的程式碼更簡潔,基於dash.ctx.triggered_id的分支判斷在很多場景下還是更合適。

  作為一個新的功能,allow_duplicate目前在常規的伺服器端回撥函數中運作正常,但在瀏覽器端回撥函數中暫時無法使用,靜待後續Dash官方的更新。

2 新增Patch()操作模式

  Dash 2.9.0版本中新增引數區域性快捷更新操作Patch(),使得我們可以在回撥函數中對目標屬性進行區域性更新,這樣說起來還是比較抽象,我們舉例說明:

  假如我們的應用中要實現這樣的互動邏輯:每點選一次AntdButton,就會在下方AntdSpace中新增一行文字內容,在以前的版本中,要實現這個功能,我們需要在回撥函數中額外將目標AntdSpacechildren屬性作為State傳入,從而在每次回撥執行時,將新的一行內容追加到先前狀態的children列表中,再進行輸出:

import dash
import uuid
from dash import html
import feffery_antd_components as fac
from dash.dependencies import Input, Output, State

app = dash.Dash(__name__)

app.layout = html.Div(
    [
        fac.AntdButton(
            '新增一行',
            id='add-new-line'
        ),
        fac.AntdSpace(
            [],
            id='target-container',
            direction='vertical',
            style={
                'width': '100%'
            }
        )
    ],
    style={
        'padding': '50px 100px'
    }
)


@app.callback(
    Output('target-container', 'children'),
    Input('add-new-line', 'nClicks'),
    State('target-container', 'children'),
    prevent_initial_call=True
)
def add_new_line(nClicks, origin_children):

    return [
        *origin_children,
        str(uuid.uuid4())
    ]


if __name__ == '__main__':
    app.run(debug=True)

  這樣做的弊端很明顯——我們每次更新都需要先取回目標屬性的現有狀態,這帶來了多餘的資源消耗,而有了Patch()模式,我們就可以將回撥函數改寫為下面的形式,實現相同的效果:

@app.callback(
    Output('target-container', 'children'),
    Input('add-new-line', 'nClicks'),
    prevent_initial_call=True
)
def add_new_line(nClicks):

    patch = dash.Patch()
    patch.append(str(uuid.uuid4()))

    return patch

  相當於在回撥函數中通過範例化Patch,建立了針對目標Output的遠端代理物件,在回撥函數中針對該代理物件的各種常用操作,都會在回撥函數執行後落實到使用者瀏覽器中的目標屬性上,這聽起來可能有些抽象,我用下面的例子展示了基於Patch可以實現的常用區域性值操作(對應程式碼受篇幅限制,請在文章開頭的github倉庫中檢視):


  以上就是本文的全部內容,對Dash應用開發感興趣的朋友,歡迎新增微訊號CNFeffery,備註「dash學習」加入我的技術交流群,一起成長一起進步。