Python Monthly Topics

Python Web UIフレームワーク Streamlitの基本

寺田 学@terapyonです。2024年10月の「Python Monthly Topics」は、Python Web UIフレームワークの1つであるStreamlitの基本的な使い方を紹介します。

2024年4月には、Python Web UIフレームワークで作るデスクトップアプリと題し、Steamlitを使ってデスクトップアプリ化をする紹介を行いました。

今回はStreamlitにフォーカスを当てて、よく使う機能を紹介します。Streamlitにはたくさんの機能があり、公式ドキュメント APIリファレンスを見ても、どの機能から使って良いのかわからないという声がありました。今回は、筆者目線でよく使うであろう機能に絞って紹介します。

Streamlitとは

StreamlitはPythonで構築できるWeb用のフレームワークです。Pythonのモジュールを定義することで、インタラクティブなWebアプリを簡単に構築できるという特徴があります。

PythonのWebフレームワークにはDjangoやFlask、FastAPIなどがあります。これらはサーバサイドフレームワークやMVCフレームワークと呼ばれています。StreamlitはDjangoやFlaskとは違い、Web UIに特化したフレームワークです。筆者は「Web UIフレームワーク」と呼んでいます。

フレームワークの違いについては、先に紹介した2024年4月公開のPython Web UIフレームワークで作るデスクトップアプリを参照してください。この記事では、Streamlit以外のWeb UIフレームワークの名前も紹介していますので参照してください。

Streamlitは、細かなデザインやフロントエンドの処理を自由に表現することよりも、簡単にWebアプリを作ることにフォーカスしたフレームワークです。特にデータ系の表現や、簡単に動的な動きを実現できることに魅力があります。

環境設定から起動方法

最初に、Streamlitの開発及び起動方法を紹介します。

開発環境

今回は、Python 3.12を用いて開発を行います。

執筆時点(2024年10月14日)において、Pythonは3.13がリリースされていますが、Streamlitが公式にサポートしているのは、Python 3.8から3.12までとなっています。

Pythonの仮想環境であるvenvを準備して、Streamlitをインストールします。ここでは、macOSのターミナルで実行をしています。venvは、WindowsやLinuxでも利用可能です。

venvの作成と有効化
% python3.12 -m venv venv
% source venv/bin/activate
(venv) % 

venvの準備と有効化が終わりましたので、Streamlitをインストールします。

Streamlitのインストール
(venv) % pip install streamlit

ここまでで、Streamlitの準備が終わりました。

Streamlitと一緒に、pandasなど多くの依存パッケージがインストールされます。

起動方法

Streamlitを起動するには以下のように実行します。その際にPythonモジュールを指定します。

Streamlitの起動
(venv) % streamlit run モジュールファイル

タイトルだけを表示するアプリ「app.py」を作ります。

app.py
import streamlit as st

st.title("サンプルアプリ")

app.pyモジュールを起動するには以下のように行います。

app.pyを起動
(venv) % streamlit run app.py

アプリを起動するとデフォルトブラウザが立ち上がり、以下のように表示されます。

app.pyを表示
app.pyを表示

サンプルアプリ ―ランダム選択アプリ

ここから、⁠スペース区切りの文字列からランダムに1つの単語を選択する」というアプリを作っていきます。

アプリケーションの内容は以下のとおりです。

タイトル 文字列から単語を選択するアプリ
説明 入力された文字列をスペース区切りで分割し、ボタンを押したら1つが選ばれる
入力 1行文字入力
結果 1つの単語
モジュール choice_app.py

ランダム選択アプリのコード

Streamlitのコードは以下のとおりです。

choice_app.py
import random
import streamlit as st

st.title("文字列から単語を選択するアプリ")
# 入力ボックス
text = st.text_input("スペース区切りで単語を入力してください。")
# スペース区切りでリスト化
words = text.replace(" ", " ").split(" ")

# リストを表示
st.write("入力した単語: ", words)

# ボタンを設置し、ボタンが押されたら実行
if st.button("一つを選ぶ"):
    choice = random.choice(words)  # ランダムに1つだけを選択
    st.write("選択された単語: ", choice)  # 選択されたものを表示

以下、choice_app.pyのコードを説明します。

  • st.text_input()
    • 1行入力(textの入力)を定義し、入力されたものを変数textに格納
  • text.replace(" ", " ").split(" ")
    • Pythonの文字列メソッドreplace()で全角スペースを半角に置き換え
    • その後、同じく文字列メソッドsplit()で半角スペースで文字列を分割してリストを作成
    • 上記の結果を変数wordsに格納
  • st.write()
    • アプリ上(ブラウザ上)に表示するための機能を使って分割された文字列を表示
    • st.write()が与えられるデータに沿って、データの内容を表示
    • st.write()の詳細は次項にて説明します
  • st.button()
    • クリック可能なボタンを設置
    • if文のあとにボタンを設置することで、ボタンが押されたときにifブロックが実行できるように設定
  • random.choice()
    • Pythonのrandomモジュールのchoice()を使ってリストの中から1つを選択可能に

ランダム選択アプリを実行した結果は以下のとおりです。

ランダム選択アプリの実行結果
ランダム選択アプリの実行結果

今回は、⁠中華、和食、イタリアン、フレンチ」の中からランダムに選び、⁠和食」が選択されました。

st.write()

st.write()機能は非常に便利な機能です。

文字列を渡せば文字列をそのまま出力してくれます。今回のサンプルコードでは、リストもst.write()に渡しています。リストの場合はリスト用の表示になっていることがわかったかと思います。

このst.write()にさまざまなデータ型を渡すと、内部で表示形式が自動で選択され、適切な表示にしてくれます。たとえば、pandasのDataFrameは表形式になりますし、Markdownを渡すとフォーマットが解釈されて自動で変換されて表示してくれます。他にも、画像(PIL.image)やgraphvizなどをきれいに表示してくれる機能があります。

一方でStreamlitには、DataFrameを表示するst.dataframe()や、画像を表示するst.image()といった専用の機能が存在します。専用の機能には、表示する大きさなどの引数があります。専用の機能を用いることで、表示をより細かく制御することができます。

サンプルアプリ ―サイコロを2つ振り結果を表示するアプリ

ここからは、もう少し複雑なアプリを作っていきます。アプリケーションの内容は以下のとおりです。

タイトル サイコロを2つ振った結果を表示するアプリ
説明 サイコロ2個を何度か振って結果を表形式で表示し、棒グラフで結果を表示する
モジュール dice_app.py
ステップ
  1. サイコロを模して2つの1~6の数値を生成
  2. 合計値を求める
  3. サイコロの値と合計値を表で表示
  4. 合計値の棒グラフを書く
  5. 「まとめてサイコロをたくさん振る」機能をスライダーウィジェットを使用して拡張

「サイコロを2つ振り結果表示アプリ」の最初のコード

ここでは最初のコードとして、上記のステップの3まで実行(合計値を表で表示)するコードを示します。

dice_app.py
import random
import streamlit as st
import pandas as pd

dices = []  # 結果を格納する空リスト

st.title("2つのサイコロを振るアプリ")

if st.button("サイコロを振る"):
    dice1 = random.randint(1, 6)  # 1-6までをランダムに生成
    dice2 = random.randint(1, 6)
    dice_sum = dice1 + dice2  # 2つのサイコロの値を合計
    dices.append((dice1, dice2, dice_sum))  # リストに結果を格納
    st.write(f"1つ目のサイコロ: {dice1} / 2つ目のサイコロ: {dice2}")

df = pd.DataFrame(dices, columns=["1つ目のサイコロ", "2つ目のサイコロ", "合計"])
st.dataframe(df)  # DataFrameを出力
st.write("試行回数: ", len(dices))

以下、dice_app.pyを説明します。

  • dices = []
    • サイコロを振った結果を格納する空リストを定義
  • random.randint(1, 6)
    • 1から6までの整数をランダムに生成
  • dices.append()
    • 各出目と合計値をタプルで保存
  • pd.DataFrame()
    • DataFrame化する。これは次の表示で表形式で表示するため
  • st.dataframe(df)
    • DataFrameを表形式で出力
    • データをCSV形式でダウンロードしたり、検索する機能がUI上に表示される

実行結果を以下に示します。

「サイコロを2つ振り結果表示アプリ」の初期状態
サイコロを2つ振り結果表示アプリの初期状態

このままででは狙い通りのアプリができていません。実行して何度か「サイコロを振る」ボタンをクリックするとわかりますが、試行回数が「1」のまま増えません。表で表現しているDataFrame上も、1件しか表示されません。

これは、Streamlitの特徴である「ボタンが押されたり入力値が変更され状態が変わったときに、モジュールのすべてが再実行される」という特徴のためです。次の項で解決策を示します。

「サイコロを2つ振り結果表示アプリ」に状態保持機能を入れる

先ほどまでのコードでは、ボタンを押すたびにモジュールが再実行されます。結果を保持しようとしていたdices = []が毎回初期化されてしまい、アプリケーションが狙い通りの動きができませんでした。

毎回初期化されないようにする方法はいくつか存在します。

  • セッションにデータを保持する
  • 関数化してキャッシュ化する
  • コードの一部だけを再実行する

今回は、セッションにデータを保持する方法で解決します。

なお、関数化してキャッシュ化するには@st.cache_data@st.cache_resourceを用います。詳しくは、公式ドキュメント キャッシュ を参照してください。

また、コードの一部を再実行する方法としては、Streamlit 1.37.0に入ったフラグメント機能を使います。詳しくは、公式ドキュメント フラグメント を参照してください。

セッションにデータを保持 するには、st.session_stateを使います。

セッションを使った変更後のコードを見てみましょう。

import random
import streamlit as st
import pandas as pd


if "dices" not in st.session_state:  # セッションデータの初期化
    st.session_state.dices = []

st.title("2つのサイコロを振るアプリ")

if st.button("サイコロを振る"):
    dice1 = random.randint(1, 6)
    dice2 = random.randint(1, 6)
    dice_sum = dice1 + dice2
    st.session_state.dices.append((dice1, dice2, dice_sum))  # セッションにappend
    st.write(f"1つ目のサイコロ: {dice1} / 2つ目のサイコロ: {dice2}")

df = pd.DataFrame(st.session_state.dices, columns=["1つ目のサイコロ", "2つ目のサイコロ", "合計"])
st.dataframe(df)
st.write("試行回数: ", len(st.session_state.dices))

変更点は以下のとおりです。

  • "dices" not in st.session_state
    • st.session_stateにdicesが存在するかを確認
  • st.session_state.dices = []
    • st.session_stateにdicesが存在しないときは、空リストで初期化
  • st.session_state.dices.append()
    • st.session_stateのdicesにデータを追記
  • DataFrame化、試行回数表示の変更

動作を確認してみます。

「サイコロを2つ振り結果表示アプリ」にセッションを導入
サイコロを2つ振り結果表示アプリにセッションを導入

ボタンが押されるたびにDataFrameの表が増え、試行回数も増えていることがわかりました。

「サイコロを2つ振り結果表示アプリ」にグラフを表示

Streamlitには、グラフを表現する機能があります。ここでは、Streamlitの標準機能を用いてグラフ化します。ここでは、2つのサイコロの和がボタンが押されるごとにどのようになるかの回数を、棒グラフで表現してみます。

グラフを表示するには、先ほどまでのコードに以下の2行を追加するだけです。

if st.button("結果を表示"):
    st.bar_chart(df["合計"].value_counts())

ここでは「結果を表示」ボタンをクリックするごとに、棒グラフを表示します。

  • st.bar_chart()
    • 棒グラフを表示
  • df["合計"].value_counts()
    • 定義済みDataFrameから、"合計"カラムを取得し、合計値が同じ回数をカウント

以下は、30回サイコロを振った結果を表示しました。

30回サイコロを振った結果を棒グラフで表示
30回サイコロを振った結果を棒グラフで表示

「サイコロを2つ振り結果表示アプリ」に複数回のサイコロをまとめて振る

このアプリの完成形として、⁠まとめてサイコロをたくさん振る」機能をスライダーウィジェットを使用して拡張してみます。

import random
import streamlit as st
import pandas as pd


if "dices" not in st.session_state:
    st.session_state.dices = []

st.title("2つのサイコロを振るアプリ")

multiple = st.toggle("複数回振る", False)  # 複数回振るかのトグルスイッチ
if not multiple:
    if st.button("サイコロを振る"):
        dice1 = random.randint(1, 6)
        dice2 = random.randint(1, 6)
        dice_sum = dice1 + dice2
        st.session_state.dices.append((dice1, dice2, dice_sum))
        st.write(f"1つ目のサイコロ: {dice1} / 2つ目のサイコロ: {dice2}")
else:
    n = st.slider("回数", 1, 1000, 500)  # 回数をスライダーで入力
    if st.button("サイコロを振る"):
        for _ in range(n):
            dice1 = random.randint(1, 6)
            dice2 = random.randint(1, 6)
            dice_sum = dice1 + dice2
            st.session_state.dices.append((dice1, dice2, dice_sum))
        st.write(f"{n}回振りました。")
if st.session_state.dices and st.button("リセット"):  # 過去のデータを削除する機能
    st.session_state.dices = []


df = pd.DataFrame(st.session_state.dices, columns=["1つ目のサイコロ", "2つ目のサイコロ", "合計"])
st.dataframe(df)
st.write("試行回数: ", len(st.session_state.dices))

if st.button("結果を表示"):
    st.bar_chart(df["合計"].value_counts())

追加した機能を説明します。

  • st.toggle("複数回振る", False)
    • トグルスイッチを設置し、複数回振るモードかを設定
    • 第2引数はデフォルト値でFalseとし、1回ずつ振るモードがデフォルト
  • st.slider("回数", 1, 1000, 500)
    • 数値を入力するスライダーを準備
    • 第2引数と第3引数で範囲を指定、1から1000回までが選択可能
    • 第4引数でデフォルト値を設定、500回がデフォルト
  • st.session_state.dices and st.button("リセット")
    • 過去の振ったサイコロの記録を削除する機能を設置
    • セッションにデータがあった場合のみ「リセット」ボタンを表示
    • リセットをクリックすると、ifブロックが実行され、セッションを初期化

完成したアプリは以下のようになります。

「サイコロを2つ振り結果表示アプリ」が完成
サイコロを2つ振り結果表示アプリ完成-1 サイコロを2つ振り結果表示アプリ完成-2

その他のStreamlitの機能

ここでは、今回のアプリで使用しなかった機能の中から、Streamlitのその他の機能を紹介します。

入力

さまざまな入力ウィジェットが準備されています。詳細は、公式ドキュメント Widget を参照してください。

この中からいくつかを紹介します。

  • テキストエリア
    • st.text_area
    • 複数行の文字列を入力
  • セレクトボックス
    • st.selectbox
    • リストから一つを選択
  • マルチセレクト
    • st.multiselect
    • リストから複数を選択
  • ファイルアップロード
    • st.file_uploader
    • ファイルをアップロード
    • ドラッグ&ドロップに対応
  • 日付入力
    • st.date_input
    • カレンダーから日付を選択
  • ダウンロードボタン
    • st.download_button
    • ボタンをクリックすると、ファイルがダウンロード

出力

出力方法もさまざまなものが準備されています。

レイアウト

サイドバーやカラムの設定、タブ切り替えにも対応しています。詳細は、公式ドキュメント Layouts and Containers を参照してください。

筆者がよく使うのは2つです。

  • タブ化
    • st.tabs
    • 複数の機能をタブで切り替える
  • サイドバー
    • st.sidebar
    • 情報や補足的な設定をサイドバーに設置

デプロイ

Streamlitはサーバ系のアプリケーションです。サーバにデプロイすることでURLを持ち、Webブラウザからインターネット経由で利用することができます。

今回は、Streamlitをホストするサービスを利用してデプロイします。

Streamlit Cloud

Streamlit公式のホスティング環境です。

Community Cloud | Streamlit
URL:https://streamlit.io/cloud

Streamlit Cloudは以下の特徴があります。

  • 無料
  • 簡単にデプロイできる
  • 認証などの機能がない

Streamlit Cloudを利用するには、ログインアカウントを作り、ダッシュボード にアクセスします。

ここでは、GitHubでアプリケーションを管理している前提で、アプリをデプロイする手順を紹介します。

  • ダッシュボードの右上の Create app をクリック
  • 「Do you already have an app?」と聞かれるので、⁠Yup, I have an app」を選択
  • GitHubのオーガニゼーションを選択し、認証
  • その後、以下の通り入力
    • Repository:GitHubのレポジトリのURLをペースト
    • Branch:ここでは⁠main⁠を選択
    • Main file path:アプリのモジュールを選択
    • App URL (optional):今回は「st-sample-gihyo-202410」と入力
  • Deployボタンをクリック
Streamlit Cloudにデプロイ
Streamlit Cloudにデプロイ

デプロイされ、App URLに入力したURLが表示されます。

今回は、以下にサイコロを振るアプリをデプロイしました。

サイコロを振るアプリ
URL:https://st-sample-gihyo-202410.streamlit.app/
サイコロを振るアプリをデプロイ
サイコロを振るアプリをデプロイ

その他の選択肢

Streamlit Cloud以外に、HuggingFace Spacesを活用する方法もあります。HuggingFace SpacesにはGPUの利用が可能な有料プランがあります。

筆者は何度かHuggingFace Spacesを利用してアプリをホストしています。普段は無料プランでホスティングしておいて、利用が多くなるときやGPUが必要なタイミングだけ有料プランに変更する、といった柔軟な運用ができることに魅力を感じています。

また、独自の環境へのデプロイには、サーバにPythonをセットアップし、Streamlitを起動するスクリプトを書いてホスティングする方法があります。他には、Dockerを使ってホスティングすることも可能です。

まとめ

今回は、PythonのWeb UIフレームワークの1つであるStreamlitを使い始めるための基本機能の紹介を行いました。Webアプリ化すると、Pythonスクリプトで作っていたものをGUIから実行可能なプログラムにできます。このような用途にもStreamlitは活用できると思います。みなさんもぜひ挑戦してみてください。

おすすめ記事

記事・ニュース一覧