酒爷爷 發表於 2021-1-22 19:57:00

(数据科学学习手札104)Python+Dash快速web应用开发——回调交互篇(上)

<blockquote>
<p>本文示例代码已上传至我的<code>Github</code>仓库https://github.com/CNFeffery/DataScienceStudyNotes</p>
</blockquote>
<ul>
<li>😋由我开源的先进<code>Dash</code>组件库<code>feffery-antd-components</code>正处于早期测试版本阶段,欢迎前往官网<code>http://fac.feffery.tech/</code>了解更多</li>
</ul>
<h1 id="1-简介">1 简介</h1>
<p>   这是我的系列教程<strong>Python+Dash快速web应用开发</strong>的第三期,在前两期的教程中,我们围绕什么是<code>Dash</code>,以及如何配合方便好用的第三方拓展<code>dash-bootstrap-components</code>来为我们的<code>Dash</code>应用设计布局展开了非常详细的介绍。</p>
<p>  而<code>Dash</code>最吸引我的地方在于其高度封装了<code>react.js</code>,使得我们无需编写<code>js</code>语句,纯<code>Python</code>编程就可以实现浏览器前端与后端计算之间常规的异步通信,从而创造出功能强大的交互式<code>web</code>应用。</p>
<center><img src="https://img2020.cnblogs.com/blog/1344061/202101/1344061-20210122195338469-1954178314.png" style="zoom: 100%"></center><center><font size="2">图1</font></center>
<p>  从今天的文章开始,我就将开始带大家走进<code>Dash</code>的核心内容——<strong>回调</strong>。</p>
<h1 id="2-dash中的基础回调">2 Dash中的基础回调</h1>
<h2 id="21-最基础的回调">2.1 最基础的回调</h2>
<p>  <code>Dash</code>中的<strong>回调</strong>(<em>callback</em>)是以装饰器的形式,配合自编回调函数,实现前后端异步通信交互,这句话可能不太好理解,我们从一个简单的例子出发来认识<code>Dash</code>中的<strong>回调</strong>:</p>
<blockquote>
<p>app1.py</p>
</blockquote>
<pre><code class="language-Python">import dash
import dash_html_components as html
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output

app = dash.Dash(
    __name__,
    external_stylesheets=['css/bootstrap.min.css']
)

app.layout = html.Div(
    [
      html.Br(),
      html.Br(),
      html.Br(),
      dbc.Container(
            [
                dbc.Row(
                  [
                        dbc.Col(dbc.Input(id='input-value',
                                          placeholder='请输入些东西'),
                              width=12),
                        dbc.Col(dbc.Label(id='output-value'),
                              width=12)
                  ]
                )
            ]
      )
    ]
)


# 对应app实例的回调函数装饰器
@app.callback(
    Output('output-value', 'children'),
    Input('input-value', 'value')
)
def input_to_output(input_value):
    '''
    简单的回调函数
    '''
    return input_value


if __name__ == '__main__':
    app.run_server()
</code></pre>
<p>  先来看看<code>app1</code>的交互效果:</p>
<center><img src="https://img2020.cnblogs.com/blog/1344061/202101/1344061-20210122195349947-1516476961.gif" style="zoom: 100%"></center><center><font size="2">图2</font></center>
<p>  下面我们来分解上面的代码,梳理一下要构造一个具有实际交互功能的<code>Dash</code>应用需要做什么:</p>
<ul>
<li><strong>确定输入与输出部件</strong></li>
</ul>
<p>  一个可交互的系统一定是有<strong>输入</strong>与<strong>输出</strong>的,我们开头导入的<code>Input</code>与<code>Output</code>对象,他们分别扮演着<strong>输入者</strong>与<strong>输出者</strong>两种角色,其各自的第一个参数<code>component_id</code>用于联动前端部分定义的部件。</p>
<p>  我们在前面定义前端部件时,为<code>dbc.Input</code>对应的输入框设置了<code>id='input-value'</code>,为<code>dbc.Label</code>对应的文字输出设置了<code>id='output-value'</code>,让它们作为第一个参数可以被<code>Input()</code>与<code>Output()</code>唯一识别出来。</p>
<ul>
<li><strong>确定输入与输出内容</strong></li>
</ul>
<p>  在确定了<strong>输入者</strong>与<strong>输出者</strong>之后,更重要的是为告诉<code>Dash</code>需要监听什么输入,响应什么输出,这就要用到第二个参数<code>component_property</code>。</p>
<p>  它与对应的前端部件有关,譬如我们的<code>dbc.Input()</code>输入框,其被输入的内容都存在<code>value</code>属性中,而<code>children</code>属性是<code>dbc.Label</code>以及绝大多数<code>html</code>部件的第一个参数,这样我们就确定了输入输出内容。</p>
<ul>
<li><strong>装饰回调函数</strong></li>
</ul>
<p>  <code>app.callback()</code>装饰器按照规定的先<code>Output()</code>后<code>Input()</code>的顺序传入相应对象,而既然是装饰器,自然需要配合自定义回调函数使用。</p>
<p>  我们的<code>input_to_output()</code>就是对应的回调函数,其参数与装饰器中的<code>Input()</code>对应,而函数内部则用来定义计算处理过程。</p>
<p>  最后<code>return</code>的对象则对应<code>Output()</code>。</p>
<pre><code class="language-Python"># 对应app实例的回调函数装饰器
@app.callback(
    Output('output-value', 'children'),
    Input('input-value', 'value')
)
def input_to_output(input_value):
    '''
    简单的回调函数
    '''
    return input_value
</code></pre>
<p>  通过上面这样的结构,我们得以纯<code>Python</code>“寥寥数语”实现了交互功能,赋予我们编写任意功能<code>Dash</code>应用的能力。</p>
<h2 id="22-同时设置多个input与output">2.2 同时设置多个Input()与Output()</h2>
<p>  在上一小节中我们介绍的是最基本的<strong>单输入 -&gt; 单输出</strong>回调模式,很多时候我们需要更复杂的回调模式,譬如下面的例子:</p>
<blockquote>
<p>app2.py</p>
</blockquote>
<pre><code class="language-Python">import dash
import dash_html_components as html
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output

app = dash.Dash(
    __name__,
    external_stylesheets=['css/bootstrap.min.css']
)

app.layout = html.Div(
    [
      html.Br(),
      html.Br(),
      html.Br(),
      dbc.Container(
            [
                dbc.Row(
                  [
                        dbc.Col(dbc.Input(id='input-value1'), width=3),
                        dbc.Col(html.P('+'), width=1),
                        dbc.Col(dbc.Input(id='input-value2'), width=3),
                  ],
                  justify='start'
                ),
                html.Hr(),
                dbc.Label(id='output-value')
            ]
      )
    ]
)


@app.callback(
    Output('output-value', 'children'),
    Input('input-value1', 'value'),
    Input('input-value2', 'value')
)
def input_to_output(input_value1, input_value2):

    try:
      return float(input_value1) + float(input_value2)
    except:
      return '请输入合法参数!'


if __name__ == '__main__':
    app.run_server()
</code></pre>
<center><img src="https://img2020.cnblogs.com/blog/1344061/202101/1344061-20210122195357567-461803297.gif" style="zoom: 100%"></center><center><font size="2">图3</font></center>
<p>  这里我们的<code>Input()</code>对象不止一个,在<code>Output()</code>对象之后依次传入(也可以把所有<code>Input()</code>对象包在一个列表中传入),其顺序对应后面回调函数的参数顺序,从而实现了多个输入值的一一对应。</p>
<p>  同样的,<code>Output()</code>也可以有多个:</p>
<blockquote>
<p>app3.py</p>
</blockquote>
<pre><code class="language-Python">import dash
import dash_html_components as html
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output

app = dash.Dash(
    __name__,
    external_stylesheets=['css/bootstrap.min.css']
)

app.layout = html.Div(
    [
      html.Br(),
      html.Br(),
      html.Br(),
      dbc.Container(
            [
                dbc.Row(
                  [
                        dbc.Col(dbc.Input(id='input-lastname'), width=3),
                        dbc.Col(html.P('+'), width=1),
                        dbc.Col(dbc.Input(id='input-firstname'), width=3),
                  ],
                  justify='start'
                ),
                html.Hr(),
                dbc.Label(id='output1'),
                html.Br(),
                dbc.Label(id='output2')
            ]
      )
    ]
)


@app.callback(
    [Output('output1', 'children'),
   Output('output2', 'children')],
    [Input('input-lastname', 'value'),
   Input('input-firstname', 'value')]
)
def input_to_output(lastname, firstname):

    try:
      return '完整姓名:' + lastname + firstname, f'姓名长度为{len(lastname+firstname)}'
    except:
      return '等待输入...', '等待输入...'


if __name__ == '__main__':
    app.run_server()
</code></pre>
<center><img src="https://img2020.cnblogs.com/blog/1344061/202101/1344061-20210122195403557-408704830.gif" style="zoom: 100%"></center><center><font size="2">图4</font></center>
<p>  可以看到不管是多个<code>Output()</code>还是<code>Input()</code>,只需要嵌套在列表中即可。</p>
<h2 id="23-利用state实现惰性交互">2.3 利用State()实现惰性交互</h2>
<p>  很多情况下,如果我们的回调函数计算过程时间开销较大,那么像前面介绍的仅靠<code>Input()</code>与<code>Output()</code>实现的前后端通信会很频繁,因为监听到的所有输入部件对应属性值只要略一改变,就会触发回调。</p>
<p>  为了解决这类问题,<code>Dash</code>中设计了<code>State()</code>对象,我们可以利用<code>State()</code>替换<code>Input()</code>来绑定对应的输入值,再将一些需要主动触发的譬如<code>dbc.Button()</code>按钮部件的属性<code>n_clicks</code>,作为<code>Input()</code>对象进行绑定。</p>
<p>  让我们通过下面的例子更好的理解它的作用:</p>
<blockquote>
<p>app4.py</p>
</blockquote>
<pre><code class="language-Python">import dash
import dash_html_components as html
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output, State

app = dash.Dash(
    __name__,
    external_stylesheets=['css/bootstrap.min.css']
)

app.layout = html.Div(
    [
      html.Br(),
      html.Br(),
      html.Br(),
      dbc.Container(
            [
                dbc.Row(
                  [
                        dbc.Col(dbc.Input(id='input-value'),
                              width=4),
                        dbc.Col(dbc.Button('小写转大写',
                                           id='state-button',
                                           n_clicks=0),
                              width=4),
                        dbc.Col(dbc.Label(id='output-value',
                                          style={'padding': '0',
                                                 'margin': '0',
                                                'line-height': '38px'}),
                              width=4)
                  ],
                  justify='start'
                )
            ]
      )
    ]
)


@app.callback(
    Output('output-value', 'children'),
    Input('state-button', 'n_clicks'),
    State('input-value', 'value')

)
def input_to_output(n_clicks, value):

    if n_clicks:
      return value.upper()


if __name__ == '__main__':
    app.run_server()
</code></pre>
<center><img src="https://img2020.cnblogs.com/blog/1344061/202101/1344061-20210122195410362-1896804360.gif" style="zoom: 100%"></center><center><font size="2">图5</font></center>
<p>  可以看到,装饰器中按照<code>Output()</code>、<code>Input()</code>、<code>State()</code>的顺序传入各个对象后,我们的<code>Button()</code>部件的<code>n_clicks</code>参数记录了对应的按钮被点击了多少次,初始化我们设置其为0,之后每次等我们输入完单词,主动去点击按钮从而增加其被点击次数记录时,回调函数才会被触发,这样就方便了我们的很多复杂应用场景~</p>
<hr>
<p>  以上就是本期的全部内容,欢迎在评论区与我进行讨论~</p><br><br>
来源:https://www.cnblogs.com/feffery/p/14313103.html
頁: [1]
查看完整版本: (数据科学学习手札104)Python+Dash快速web应用开发——回调交互篇(上)