Python {Article137}

ようこそ「Python」へ...

Python: Dashのコールバック機能を理解する【Dash超入門】

ここでは、はじめてDash(※)を学習しようとする方への超入門編として、Dashのコールバック機能の使い方について解説します。 今回作る、Webアプリはものすごく単純で、 ブラウザに表示されているテキストボックスに名前を入力して[送信]ボタンをクリックすると「Hello {名前}」を表示します。 最初は、基本機能のみ組み込んで徐々に機能を追加していきます。

最初は、@appディレクティブのコールバック「callback()」に引数として「Output(), Input()」を指定します。 「Input()」には、Webページがポストバックされたときに取得するコンポーネントのIDとプロパティを指定します。 ここでは、「送信」ボタンと「テキストボックス」のIDとプロパティを指定しています。 ボタンのプロパティ「n_clicks」にはボタンをクリックした回数が格納されます。デフォルト値は「0」です。 テキストボックスのプロパティ「value」には、テキストボックスに入力したデータが格納されます。

「Output()」には、Webページに出力(表示)するコンポーネントのIDとプロパティを指定します。 ここでは、HTMLのDiv要素のID「my-output」とプロパティ「children」を指定しています。
### コールバックを定義する
@app.callback(
    Output(component_id='my-output', component_property='children'),
    Input(component_id='my-submit', component_property='n_clicks'),
    Input(component_id='my-input', component_property='value')
)
def update_output_div(n_clicks, input_value,):
    # コールバックが呼ばれたことを確認する
    print(f"{n_clicks=}, {input_value=}")
    # ボタンがクリックされたとき、入力された名前を表示する
    if n_clicks:
        return f"Hello {input_value}"  
次に、@appディレクティブの引数に「State()」を使用する方法を解説します。 テキストボックスに「State()」を使用すると、 テキストボックスにデータを入力中にポストバックされるのを抑止することができます。 つまり、ムダなポストバックを発生させないようにします。
@app.callback(
    Output(component_id='my-output', component_property='children'),
    Input(component_id='my-submit', component_property='n_clicks'),      
    State(component_id='my-input', component_property='value') 
) 
def update_output_div(n_clicks, input_value,):
    print(f"{n_clicks=}, {input_value=}")
    if n_clicks:
        return f"Hello {input_value}"      
最後に、コールバック関数「update_output_div()」で、 Webページが初期ロードされたのか、それともポストバックされたのかをチェックして、 初期ロードされたとき「Please enter...」のメッセージを表示します。
@app.callback(
    Output('my-output', 'children'),
    Input('my-submit', 'n_clicks'),      
    State('my-input', 'value') 
)
def update_output_div(n_clicks, input_value,):    
    if ctx.triggered:
        if n_clicks:
            return f"🙋Hello {input_value}"
    else:
        return 'Please enter your name and click [Submit] button!'    
※ Dashは、PythonでWebアプリケーションを作成するためのフレームワークです。 Plotlyのグラフ描画ライブラリを利用しており、データを視覚化するために最適化されています。 Dashは、PythonのWebフレームワークであるFlaskをベースにしており、 Pythonの標準ライブラリを使用しているので、 パフォーマンスが高く、実行速度が速いといった特徴があります。

Dashは、Pythonを使用したデータ分析やデータ視覚化のためのツールとして広く使用されており、 ビジネスインテリジェンス、科学、エンジニアリング、金融、医療、教育などのさまざまな分野で使用されています。 Dashを使用することで、カスタマイズされたWebアプリケーションを比較的簡単に作成できます。 Dashは、PythonによるWebアプリケーション開発において選択肢にいれておきたい開発ツールのひとつです。

説明文の左側に図の画像が表示されていますが縮小されています。 画像を拡大するにはマウスを画像上に移動してクリックします。 画像が拡大表示されます。拡大された画像を閉じるには右上の[X]をクリックします。 画像の任意の場所をクリックして閉じることもできます。
click image to zoom!
図A Callback(Input)
click image to zoom!
図B Callback(n_clicks)
click image to zoom!
図C Style
click image to zoom!
図D Bootstrap5
click image to zoom!
図E ctx_triggered

ブラウザから「名前」を入力して[送信]ボタンをクリックしたら「Hello ●●●」を表示するWebアプリを作る

  1. まずは、Pythonの開発環境を準備する

    ここでは、Pythonの仮想環境を作成して各種ライブラリをインストールする手順を解説します。 作業は以下の手順にて行います。
    • Pythonのプロジェクトフォルダを作成する
    • Pythonの仮想環境を作成する
    • Visual Studio Codeが今回作成した仮想環境を使用するように設定する
    • 仮想環境にDashの各種ライブラリをインストールする


    リスト1:準備で使用する各種コマンド
    # Python 3.11.x用の仮想環境を作成するコマンド(パス名は各自の環境に合わせて読み替えてください)
    C:\Users\ikasa\AppData\Local\Programs\Python\Python311\python.exe  -m venv venv
    
    # pipを最新版に更新するコマンド
    python.exe -m pip install --upgrade pip
    
    # Dashに必要な各種ライブラリをインストールするコマンド
    pip install numpy
    pip install pandas
    pip install matplotlib
    pip install dash
    pip install dash-bootstrap-components   
    click image to zoom!
    図1-1
    まずは、Pythonのプロジェクトフォルダを作成します。 ここでは「DashTemp」のフォルダを作成します。

    フォルダを作成したら、Visual Studio Code(VS Code)を起動して、 「File」メニューから「Open Folder...」を選択します。

    click image to zoom!
    図1-2
    「Open Folder」ダイアログが表示されたら「DashTemp」フォルダを選択します。

    click image to zoom!
    図1-3
    VS Codeの「Terminal」メニューから「New Terminal」を選択します。

    click image to zoom!
    図1-4
    「Terminal」ウィンドウが表示されたら、「リスト1」に掲載されているコマンドを参照して、 「C:\...\python.exe -m venv venv」を入力して「Enter」で実行します。 ここでは、Python 3.11.xの仮想環境を作成しています。

    「python.exe」のパス名は各自の環境に合わせて書き換えてください。 仮想環境の作成が完了すると新規フォルダ「venv」が作成されます。

    click image to zoom!
    図1-5
    「ゴミ箱」のアイコンをクリックして「Terminal」ウィンドウを閉じます。

    click image to zoom!
    図1-6
    [F1」キーを押して「コマンドリスト」を表示したら「Python: Select Interpreter」を選択します。

    click image to zoom!
    図1-7
    次に「+Enter interpreter path...」を選択します。

    click image to zoom!
    図1-8
    さらに「Find...」を選択します。

    click image to zoom!
    図1-9
    「Select Python interpreter」ダイアログが表示されたら、「DashTemp/venv/Scripts」フォルダの「phthon.exe」を選択します。 これで、VS Codeは仮想環境のPython 3.11.xを使用するようになります。

    click image to zoom!
    図1-10
    VS Codeの「Terminal」メニューから「New Terminal」を選択して再度「Terminal」ウィンドウを開きます。 「Terminal」ウィンドウに「(venv)」が表示されたら仮想環境が正常に設定されたことになります。

    click image to zoom!
    図1-11
    ここからは、「リスト1」に掲載されている「pip install」コマンドを順番に入力して実行します。

    click image to zoom!
    図1-12
    図1-12には、全てのライブラリをインストールした状態の画面が表示されています。 これで準備作業が完了しました。
  2. Visual Studio Codeを起動してプログラムファイルを作成する

    Pythonの開発環境の準備が完了したら、 VS Codeを起動して新規のPythonファイル(*.py)を作成します。 ここで作成したPythonのファイルには「リスト2」のコードをコピペします。

    [音声でコードの解説を聞く!]ボタンをクリックして音声が聞こえたら画面をスクロールして該当する行番号に移動してください。 これで、スマホやパソコンでストレスなくソースコードを理解することができます。



    リスト2: Article137.py
    ### Dashのライブラリから必要なものをインポートする
    from dash import Dash, html, Output, Input, State, dcc, ctx
    import dash_bootstrap_components as dbc
    
    ### Dashアプリケーションを生成する
    app = Dash(__name__)
    
    ### レイアウトを定義する
    app.layout = html.Div([
        # 大見出しを定義する
        html.H1(children='Dash Hello World!'),
        
        html.Div(
        [
            # 名前を入力するフィールドを定義する
            'Name: ',
            dcc.Input(
                id='my-input',
                type='text',  # value='',
                placeholder='please enter your name...'
            ),
            # ボタンを定義する
            html.Button('Submit', id='my-submit', n_clicks=0)
        ]),
        
        # 横線を入れる
        html.Hr(),
        
        # 出力先のdivを定義する
        html.Div(id='my-output', children=[])
    ])
    
    ### コールバックを定義する
    @app.callback(
        Output(component_id='my-output', component_property='children'),
        Input(component_id='my-submit', component_property='n_clicks'),
        Input(component_id='my-input', component_property='value')
    )
    def update_output_div(n_clicks, input_value,):
        # コールバックが呼ばれたことを確認する
        print(f"{n_clicks=}, {input_value=}")
        # ボタンがクリックされたとき、入力された名前を表示する
        if n_clicks:
            return f"Hello {input_value}"
    
    ### サーバーを実行する
    if __name__ == '__main__':
        app.run_server(debug=True)  
    click image to zoom!
    図2
    図2は、VS Codeの画面です。 PythonのコードがVS Codeの編集画面に表示されています。
  3. プログラムを起動してブラウザにアプリを表示する

    ここでは、「リスト2」に掲載されてプログラムを実行して見ます。 「Dash」のプログラムはWebアプリなので、Web サーバー上で実行されます。 VS Codeからプログラムを起動すると、自動的に「Web サーバー」が起動されてURLのリンクが表示されます。

    このリンクをクリックするとブラウザ上にWebページが表示されます。 DashのWebページは、全て実行時にダイナミックに生成されます。 なので、HTMLファイル「index.html」などは作成する必要ありません。 操作手順は図で説明します。

    click image to zoom!
    図3-1
    VS Codeの右上から「▷」ボタンをクリックしてプログラムを起動します。 「Dash is running on http://....」が表示されたら[Ctrl]を押しながらマウスでURLのリンクをクリックします。

    click image to zoom!
    図3-2
    図3-2では、ブラウザ上にアプリのWebページが表示されています。 Webページのテキストボックスに貴方の名前を入力して見てください。 [Submit(送信)」ボタンをクリックしていないのに、 VS Codeの「Terminal」ウィンドウには入力中の名前が表示されています。 これがなぜ問題か分かりますか?

    テキストボックスに名前を入力中に、 その都度ポストバック処理(ブラウザがWebページの情報をWebサーバーに送信する)が発生しているので無駄な処理をしていることになります。 本来、入力中はポストバックさせないで送信ボタンをクリックしたときだけ、ポストバックさせるようにすべきです。 この問題を解決する方法は次のステップで解説します。

    ちなみに、なぜテキストボックスに名前を入力中にポストバックが発生するか分かりますか? Dashは、Webページをブラウザに送信するとき、JavaScriptのライブラリも同時に送ります。 このJavaScriptがテキストボックスに各種イベントを登録していろんな事象が発生するのを監視しています。

    そして、テキストボックスの値が変わったとき、JavaScriptのイベントが発生します。 JavaScriptのイベントは、この情報をサーバーに送信する必要があるかどうかをチェックします。 今回のケースでは、コールバックの「Input()」でテキストボックス「my-input」の指定があるので、 テキストボックスに入力したデータをサーバーに送信する必要があると判断してポストバックさせます。

    click image to zoom!
    図3-3
    図3-3には、Webページから[Submit(送信)]ボタンをクリックしたときの画面が表示されています。 テキストボックスに入力した名前「akio kasai」が「Hello akio kasai」と表示されています。

    Webページから「送信」ボタンをクリックすると、ブラウザはWebページの情報をWebサーバーに送信します。 この処理を「ポストバック」と呼びます。 Webサーバーは、Webページの情報(名前)を取得したらコールバック関数「update_outout_div()」にコントロールを渡します。

    コールバック関数では、「input_value」に格納されている名前「akio kasai」を取得したら「Hello」と結合させて戻します。 コールバックの「Output()」の引数には、HTMLのDiv要素のID「my-output」とプロパティ「children」を指定しているので、 「Hello akio kasai」はDiv要素に格納されます。 そして、ブラウザにWebページを送信します。 その結果、ブラウザには「Hello akio kasai」が表示されます。
  4. Dashのコールバック関数に「State」を追加して無駄なポストバックを回避する

    ここでは、Webページのテキストボックスに名前を入力中にポストバックさせないために、 コールバックの「Input()」を「State()」に変更します。 それでは、実際にプログラムを起動して確認して見ましょう。

    @app.callback(
        Output(component_id='my-output', component_property='children'),
        Input(component_id='my-submit', component_property='n_clicks'),      
        Input(component_id='my-input', component_property='value') 
    )
        ⇓ ⇓ ⇓
    @app.callback(
        Output(component_id='my-output', component_property='children'),
        Input(component_id='my-submit', component_property='n_clicks'),      
        State(component_id='my-input', component_property='value') 
    )   
    click image to zoom!
    図4-1
    図4-1は、ブラウザのテキストボックスに名前「akio kasai」を入力してときの画面です。 名前の入力が完了していますが、VS Code「Teminal」ウィンドウには名前が表示されていません。 これで、テキストボックスに名前を入力中はポストバックされないことが確認できました。

    click image to zoom!
    図4-2
    図4-2は、Webページから[Submit(送信)]ボタンをクリックしたときの画面です。 ボタンをクリックしたときは、ポストバックが発生して、コールバック関数にコントロールが渡っています。
  5. スタイルシート(CSS)を追加してWebページに表示される位置を調整する

    ここでは、スタイルシート(CSS)を追加して、Webページのレイアウトを調整します。 CSS「{'margin-left': '20px'}」は、親のDiv要素と、Button要素に追加しています。 ちなみに、DashのCSSはPythonのDict(辞書)型で指定します。

    COLORS = {
        'margin-left': '20px',
        'background': '#111111',
        'text': '#7FDBFF'
    }
    
    app = Dash(__name__)
    
    app.layout = html.Div([
        html.H1(children='Dash Hello World!'),
        html.Div([
            'Name: ',
            dcc.Input(
                id='my-input', 
                type='text',
                placeholder='please enter your name...'
            ),
            html.Button('Submit', id='my-submit', style={'margin-left': COLORS['margin-left']})
        ]),
        html.Hr(),
        html.Div(id='my-output')
    ], style={'margin-left': COLORS['margin-left']})    
    click image to zoom!
    図5
    図5には、今回追加したCSSを適用したときのWebページが表示されています。 親のDiv要素と送信ボタンの左側に「20px」分の隙間(マージン)が確保されているのが確認できます。
  6. Bootstrap 5のCSSクラスを使用してWebページに表示される位置を調整する

    ここでは、カスタムCSSの代わりにBootstrap 5のスタイルシート(CSS)を使用します。 CSSを直接Pythonのプログラムで定義するのではなく、 CSSのクラスを使用して保守性を容易にします。 この場合、Pythonのプログラムを一切変更せずにWebページのレイアウトを調整することが可能になります。 したがって、保守性が良くなります。

    Bootstrap5のスタイルシートは、 Dash()のインスタンスを生成するときに引数「external_stylesheets=」に指定します。 ここでは、「dbc.themes.UNITED」を指定していますが、さまざまなテーマを選択することができます。 さらに、「Dash Hello World!」と「Hello {input_value}」には絵文字「🪩」「🙋」を追加しています。

    app = Dash(__name__, external_stylesheets=[dbc.themes.UNITED]) 
    
    app.layout = html.Div([
        html.H1(children='🪩Dash Hello World!'),
        html.Div([
            'Name: ',
            dcc.Input(
                id='my-input', 
                type='text',
                placeholder='please enter your name...'
            ),
            html.Button('Submit', id='my-submit', className='ms-3')
        ]),
        html.Hr(),
        html.Div(id='my-output')
    ], className='ms-5')    
    
    @app.callback(
        Output('my-output', 'children'),
        Input('my-submit', 'n_clicks'),      
        State('my-input', 'value') 
    )
    def update_output_div(n_clicks, input_value,):
        if n_clicks:
            return f"🙋Hello {input_value}"
    
    if __name__ == '__main__':
        app.run_server(debug=True)   
    click image to zoom!
    図6
    図6には、Bootstrap5のスタイルシート「themes.UNITED」を適用したときのWebページが表示されています。 絵文字も表示されています。
  7. コールバック関数でWebページが初期ロードされたのかポストバックされたのかを調べる

    ここでは、コールバック関数でWebページが初期ロード「ctx.triggered==False」されたのか、 それともポストバック「ctx.triggered==True」されたのかをチェックしています。 Webページが初期ロードされたときは、「Please enter your name and click [Submit] button!」を表示します。

    @app.callback(
        Output('my-output', 'children'),
        Input('my-submit', 'n_clicks'),      
        State('my-input', 'value') 
    )
    def update_output_div(n_clicks, input_value,):    
        if ctx.triggered:
            if n_clicks:
                return f"🙋Hello {input_value}"
        else:
            return 'Please enter your name and click [Submit] button!'  
    click image to zoom!
    図7-1
    図7-1には、Webページが初期ロード(ctx.triggered==False)されたときの画面が表示されています。 Webページに「Please enter ...」が表示されています。

    click image to zoom!
    図7-2
    図7-2には、Webページから送信ボタンをクリックしたときの画面が表示されています。 送信ボタンをクリックしたときはWebページがポストバック(ctx.triggered==True)されます。
  8. 最終版のすべてのコードを掲載

    ここでは最終版の全てのコードを掲載しています。 最終版では、各種メソッドの「引数名」を省略しています。 さらに、デフォルトのプロパティなども省略しています。
    # Article137:
    from dash import Dash, html, Output, Input, State, dcc, ctx
    import dash_bootstrap_components as dbc
    
    app = Dash(__name__, external_stylesheets=[dbc.themes.UNITED]) # UNITED, BOOTSTRAP
    
    app.layout = html.Div([
        html.H1(children='🪩Dash Hello World!'),
        html.Div([
            'Name: ',
            dcc.Input(
                id='my-input', 
                type='text',
                placeholder='please enter your name...'
            ),
            html.Button('Submit', id='my-submit', className='ms-3' )
        ]),
        html.Hr(),
        html.Div(id='my-output')
    ], className='ms-5')    
    
    @app.callback(
        Output('my-output', 'children'),
        Input('my-submit', 'n_clicks'),      
        State('my-input', 'value') 
    )
    def update_output_div(n_clicks, input_value,):
        if ctx.triggered:
            if n_clicks:
                return f"🙋Hello {input_value}"
        else:
            return 'Please enter your name and click [Submit] button!'
    
    if __name__ == '__main__':
        app.run_server(debug=True)