在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公众号。

相关推荐
Logintern091 天前
什么时候应该用多进程什么时候用多线程呢?
开发语言·python
无凭1 天前
字节跳动 DeerFlow:Agent Harness 怎么让大模型主动向用户提问?
人工智能·python
蜀道山老天师1 天前
Python + Playwright 实现问卷星自动化填写
python
Zane19941 天前
多开几个线程,为什么算数字反而没变快?一文讲透 CPython 的 GIL
后端·python
W_326001 天前
Python-OpenCV边缘检测与阈值分割:Sobel、Scharr、Laplacian、Canny、全局与自适应阈值
开发语言·图像处理·python·opencv·机器学习
青 春 记 忆1 天前
LeetCode 121. 买卖股票的最佳时机|Python 解法详解
python·算法·leetcode
En^_^Joy1 天前
Django模板与静态文件使用指南
python·django·sqlite
满怀冰雪1 天前
21-图像分类实战:从 MNIST 到 CIFAR-10
人工智能·python·深度学习·分类·数据挖掘·paddlepaddle
Logintern091 天前
Celery 的底层架构正是进程池、事件循环、epoll 和协程全部组合在了一起
python·架构·消息队列·进程·celery·事件循环
用户0332126663671 天前
在 Word 文档快速中插入图片【Python 教程】
python