在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不同的参数值内容的回调函数,按照常规的写法,对应的回调函数可以写作下方形式:

Python 复制代码
@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角色进行字典化编排时,我们可以通过自定义的键值对,完成针对回调函数输入参数的映射,改造后的示例回调函数如下:

Python 复制代码
@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时不受限制),示例写法如下:

Python 复制代码
@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 嵌套式字典化角色编排

当我们在使用上文所介绍的字典化角色编排方式时,除了在字典中平铺书写相应角色外,还可以向下继续进行字典嵌套,从而实现更自由的参数分组效果,相应的,对应输入参数也会以字典的形式传入内部的各键值对参数:

Python 复制代码
@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回调的上下文简化相关过程:

Python 复制代码
@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公众号。

相关推荐
databook1 小时前
Manim实现闪光轨迹特效
后端·python·动效
Juchecar3 小时前
解惑:NumPy 中 ndarray.ndim 到底是什么?
python
用户8356290780513 小时前
Python 删除 Excel 工作表中的空白行列
后端·python
Json_3 小时前
使用python-fastApi框架开发一个学校宿舍管理系统-前后端分离项目
后端·python·fastapi
数据智能老司机10 小时前
精通 Python 设计模式——分布式系统模式
python·设计模式·架构
数据智能老司机11 小时前
精通 Python 设计模式——并发与异步模式
python·设计模式·编程语言
数据智能老司机11 小时前
精通 Python 设计模式——测试模式
python·设计模式·架构
数据智能老司机11 小时前
精通 Python 设计模式——性能模式
python·设计模式·架构
c8i11 小时前
drf初步梳理
python·django
每日AI新事件11 小时前
python的异步函数
python