Streamlitとは
StreamlitはPython向けのWebアプリケーションフレームワークです。
Streamlitを使うとフロントエンドの知識ゼロでもデータを可視化するためのWebアプリケーションを簡単に作れます。以下のような機能が特徴。
- 変数を地の文に書くだけで、GUIに出力される(マジックコマンド)
-
st.radio
やst.text_input
のようなWidget APIを呼び出すだけで、GUIコンポーネントが生成される
Streamlitの最新バージョン
Streamlitの最新バージョンはChangelogで確認できます。
2024/12/24時点で最新バージョンは1.41.1です。
以下に記載した変更点は、1.41.0でアナウンスされたものの抜粋です。
-
st.metric
およびst.columns
にボーダーを描画 - Markdownのテキストの色としてStreamlitの
theme.primaryColor
を参照 - DataFrameの列の固定
- ターシャリ(tertiary)ボタン
- パス指定に
pathlib.Path
を使用 -
st.date_input
およびst.time_input
の初期値をISO形式の文字列で指定 -
st.write_stream
にAsync Generatorを渡せる - エラーメッセージの抑止設定
-
st.line_chart
にツールチップを表示 -
st.html
が_repr_html_()
を呼び出せるか試行するように変更 - Python 3.13のサポート
見た目や使い勝手が着実に改善されています。
st.metric
およびst.columns
にボーダーを描画
st.metric
およびst.columns
でボーダーを描画するオプションが追加されました。
それぞれデザイン的に使いやすいシーンがありそうですね。
import streamlit as st
st.subheader("st.columes()でボーダーを追加する")
left, middle, right = st.columns(3, border=True)
left.markdown("この枠の外側にボーダーが描画されます。" * 5)
middle.markdown("この枠の外側にボーダーが描画されます。" * 5)
with right:
st.write("複数のコンポーネントがあっても大丈夫です。")
st.write("2つめのコンポーネント")
st.write("3つめのコンポーネント")
import pandas as pd
import numpy as np
chart_data = pd.DataFrame(np.random.randn(20, 3), columns=["a", "b", "c"])
st.line_chart(chart_data)
st.divider()
st.subheader("st.metric()でボーダーを追加する")
# 従来のst.metric()
st.metric("従来のst.metric", 100)
# ボーダーを追加する
st.metric("新しいオプションを使ったst.metric", 100, border=True)
Markdownのテキストの色としてStreamlitのtheme.primaryColor
を参照
Streamlitでは以下のサンプルのようにMarkdownでテキストの色が指定できるのですが、こちらにテーマで指定した値を参照できるようになりました。
import streamlit as st
st.markdown("## Markdownにおける色指定")
markdown_text = '''
:red[Streamlit] :orange[では] :green[Markdown] :blue[で] :violet[色指定]
:gray[が] :rainbow[できるということを知っていましたか?]
:blue-background[背景色も変更できます。]
そして…
:primary[Streamlitで指定したprimaryカラーも使えるようになりました。]
'''
st.markdown(markdown_text)
st.write('実際のMarkdown:')
st.code(markdown_text)
こんな感じで表示されます。
ちなみにtheme.primaryColor
というのはStreamlitの右上のハンバーガーメニューから設定できるテーマの設定値の一つです。
このテーマ、もっと色々な設定ができるように検討が進められているみたいですよ。
DataFrameの列の固定
st.dataframe
およびst.data_editor
で列を固定できるようになりました。
横に長いDataFrameを表示するときに威力を発揮します!
DataFrameの内容があらかじめ分かっているときが特に使いやすいですが、内容が未知であってもユーザに固定する列を選択させることも考えられますね。
ちなみに、飛び飛びの列を固定するとそれらの列は左側に寄せて表示されます。
import streamlit as st
import pandas as pd
import numpy as np
# 横に長いDataFrameを作成する。カラム名はaからzまでとする
columns_src = "abcdefghijklmnopqrstuvwxyz"
if "df" not in st.session_state:
st.session_state.df = pd.DataFrame(
np.random.randn(12, len(columns_src)), columns=list(columns_src)
)
st.dataframe(
st.session_state.df,
# カラムaとカラムcを固定する
column_config = {
"a": st.column_config.NumberColumn(pinned=True),
"c": st.column_config.NumberColumn(pinned=True)
}
)
st.data_editor(
st.session_state.df,
# カラムaのみを固定する
column_config = {
"a": st.column_config.NumberColumn(pinned=True),
}
)
ターシャリ(tertiary)ボタン
st.button
、st.download_button
、st.link_button
およびst.form_submit_button
の見た目をターシャリ(tertiary1)ボタンにするtertiary
オプションが追加されました。
非常に目立つ見た目のprimary
ボタン、普通な見た目のsecondary
ボタンに対してまったく目立たない見た目なのがターシャリボタンです。実際に試してみると、ボーダーのないボタンが表示されます。
普段は使わないんだけどいざというときにすぐアクセスしたいんだよね、という機能を割り当てるのに適しています。例えば編集や印刷などに切り替えるボタンとして使えるかも。
import streamlit as st
# 以下のボタンのそれぞれについて、typeオプションなしと、"primary"、"secondary"、"tertiary"を指定するサンプルコード
# st.button
# st.download_button
# st.link_button
c1, c2, c3 = st.columns([1, 1, 1], border=True)
with c1:
st.write("st.button")
st.button("オプションなし", use_container_width=True)
st.button("primary", type="primary", use_container_width=True)
st.button("secondary", type="secondary", use_container_width=True)
st.button("teriary", type="tertiary", use_container_width=True)
with c2:
st.write("st.download_button")
st.download_button("オプションなし", "download", use_container_width=True)
st.download_button("primary", "download", type="primary", use_container_width=True)
st.download_button("secondary", "download", type="secondary", use_container_width=True)
st.download_button("teriary", "download", type="tertiary", use_container_width=True)
with c3:
st.write("st.link_button")
st.link_button("オプションなし", "https://streamlit.io", use_container_width=True)
st.link_button("primary", "https://streamlit.io", type="primary", use_container_width=True)
st.link_button("secondary", "https://streamlit.io", type="secondary", use_container_width=True)
st.link_button("teriary", "https://streamlit.io", type="tertiary", use_container_width=True)
# サンプルコードには含まないが、st.form_submit_buttonについても同様にtypeオプションが使える
パス指定にpathlib.Path
を使用
ファイル名を指定するコンポーネントにpathlib.Path
をそのまま渡せるようになりました。今までは文字列にキャストする必要があったんですね。
数ファイルで完結する小規模なアプリだとあまり恩恵はないですが、ディレクトリ構造が複雑になってくると嬉しいですね。
import streamlit as st
from pathlib import Path
# Pathlibでファイルパスを操作する
# カレントディレクトリのオブジェクトを作成して、そこから"Pathlib_test.png"というファイルを指定
# 試すためにはカレントディレクトリに"Pathlib_test.png"という画像ファイルを置いておく必要がある
path = Path.cwd() / "Pathlib_test.png"
# ファイルが存在するか確認
if path.exists():
st.write(f"{path} exists")
# ファイルを表示
st.image(path)
st.date_input
およびst.time_input
の初期値をISO形式の文字列で指定
日付や時間を指定するとき、これまではdatetime
型を指定していたのですが、ISO形式の文字列で指定できるようになりました。
また、Pandasから日付や時間を渡すときもキャストが不要になったので地味に便利に。
import streamlit as st
import datetime
import pandas as pd
# これまでの使い方
result = st.date_input("日付指定", datetime.date(2024, 12, 23))
st.write(result)
# 新しい使い方
result = st.date_input("日付指定", "2024-12-23")
st.write(result)
# PandasのTimestampを入力する事例
date = pd.Timestamp("2024-12-23 12:34:56")
st.write(date)
result = st.time_input("時間指定", date)
st.write(result)
st.write_stream
にAsync Generatorを渡せる
意欲的な機能ですね。
LLMが回答をちょっとづつ出力しているとき、例えばAsync Generatorとして受け取ることになりますが、これをそのままst.write_stream
に渡すことができるようになりました。
st.write_stream
に渡されたAsync Generatorは、内部で通常のGeneratorに変換されるので、並列処理的な意味では嬉しさはないのですが、インタフェースを変換する必要がなくなりましたね。
import streamlit as st
import asyncio
import time
# Async Generatorのサンプル。total_len文字のランダムな文字列を生成する
async def async_generator(total_len: int):
import random
src_str = "abcdefghijklmnopqrstuvwxyz"
ret_str = ""
for i in range(total_len):
if len(ret_str) == 0:
# 0.2秒待つ
await asyncio.sleep(0.2)
# ランダムな文字を生成してret_strに追加
ret_str += random.choice(src_str)
# ret_strが5文字になったらyieldする
if len(ret_str) == 5:
yield ret_str
ret_str = ""
# 最後にret_strが残っていたらyieldする
if len(ret_str) > 0:
yield ret_str
# これまではこういう感じのGeneratorに対応していた
def sync_generator(total_len: int):
import random
src_str = "abcdefghijklmnopqrstuvwxyz"
ret_str = ""
for i in range(total_len):
if len(ret_str) == 0:
# 0.2秒待つ
time.sleep(0.2)
# ランダムな文字を生成してret_strに追加
ret_str += random.choice(src_str)
# ret_strが5文字になったらyieldする
if len(ret_str) == 5:
yield ret_str
ret_str = ""
# 最後にret_strが残っていたらyieldする
if len(ret_str) > 0:
yield ret_str
# 以下のいずれの表示にも対応
st.write("Async Generator:")
st.write_stream(async_generator(100))
st.write("Generator:")
st.write_stream(sync_generator(100))
# 以下は実装についての詳細解説のためのコード
st.divider()
st.subheader("Async Generatorのサンプル")
# 内部の実装としてはこうなっている
from typing import Any, AsyncGenerator, Generator
def async_generator_to_sync(async_gen: AsyncGenerator[Any, Any]) -> Generator[Any, Any, Any]:
# イベントループを作成する。他にイベントループがないといいなー
loop = asyncio.new_event_loop()
try:
# イベントが発生するたびにyeildする
while True:
try:
yield loop.run_until_complete(async_gen.__anext__())
except StopAsyncIteration:
# Async Generatorが終了するとこの例外が発生する
break
finally:
loop.close()
# 上記のasync_generator_to_syncを使って、Async GeneratorをGeneratorに変換して表示する
generator = async_generator_to_sync(async_generator(100))
for text in generator:
st.write(text)
エラーメッセージの抑止設定
エラーメッセージが以下の4種類から選べるようになりました。
-
full
:Exception
も含めてすべて表示します。 -
stacktrace
:Exception
の詳細を伏せます。ただ、スタックトレース(Traceback)は表示されるので、意味は薄いかも。コンソールにはすべてログが出力されています。 -
type
:Exception
名のみ表示されます。これだと内部構造や変数名が分かりにくくなりますね。 -
none
:エラーが起こったことのみ表示されます。
内部構造や変数名、ファイルパスなどが悪意のある攻撃者にバレるとリスクがあるので、アプリをリリースするときはエラーメッセージを抑止するのがいいでしょう。
import streamlit as st
# helloというオブジェクトはないので必ずエラーが発生する
st.write(hello)
具体的な表示は順に以下のようになります。
設定は.streamlit/config.toml
に記載しましょう。
[client]
showErrorDetails = "stacktrace"
st.line_chart
にツールチップを表示
st.line_chart
にマウスカーソルの位置にツールチップが表示されるようになったぞ!
ちょっとリッチなグラフモジュールだと元々対応していたのですが、一番お手軽なst.line_chart
でも対応してくれたのは嬉しいですね。
import streamlit as st
import pandas as pd
import numpy as np
chart_data = pd.DataFrame(np.random.randn(20, 3), columns=["a", "b", "c"])
st.line_chart(chart_data)
st.html
が_repr_html_()
を呼び出せるか試行するように変更
_repr_html_()
はそのオブジェクトをHTML形式でレンダリングする関数で、特にJupyter Notebookの世界でよく使われています。
この関数を持っているオブジェクトをst.html
に渡すと_repr_html_()
が返したHTMLを描画してくれるようになりました。
import streamlit as st
from htbuilder import div, ul, li
import seaborn as sns
# htbuilderを使ってHTMLを生成
dom = (
div(id='container')(
ul(_class='greetings')(
li('こんにちは'),
li('やあ!'),
li('どしたん?'),
)
)
)
# 生成したHTMLを表示
st.html(dom)
st.divider()
# Seabornで試してみる
tips = sns.load_dataset('tips')
st.html(tips)
Jupyter Notebookでは以下のような動作をします。
なお、手元で試した範囲では、なんでもいい感じに表示してくれるという訳ではないようです。
_repr_html_()
の出力に含まれているJavaScriptでグラフを描画する、みたいなものは上手く表示できませんでした。
Python 3.13のサポート
正式にPython 3.13をサポートするようになりました。
一方、Python 3.8がサポート対象外になっています。
サポート対象外というだけでしばらく前からPython 3.13でも動いていたのですが…試してみましょう。
import streamlit as st
import base64
# バイト列を文字列に変換する関数
def bytes2str(b):
return b.decode("utf-8")
# 適当なバイト列を生成する
source_bytes = base64.b64encode("b64encodeでバイト列を生成する".encode("utf-8"))
# このコードはPython3.12以前でも動作する
result = base64.b32encode(source_bytes)
result_str = bytes2str(result)
st.write(result_str)
# このコードはPython3.12以前だとエラーになる。関数が実装されたのがPython3.13だから
result = base64.z85encode(source_bytes)
result_str = bytes2str(result)
st.write(result_str)
Streamlitを試すためには?
StreamlitはPythonライブラリなので、pip
で簡単に導入できます。
pip install streamlit
streamlit run (ソースコード名).py
過去にインストールしたバージョンからアップデートする場合はpip install -U streamlit
を実行してください。
また、Python環境の構築にハードルを感じる場合は、Streamlit Playgroundで試してみるのがオススメです。こちらではブラウザ上でソースコードを入力して試すことができます。
2024/12/24時点ではStreamlit Playgroundの内部で使っているプログラム stlite が1.41.0に対応していないため、今回紹介した機能は使えません。対応を心待ちにしましょう!
-
「三次」「3位の」みたいな意味 ↩