7
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Streamlitとは

StreamlitはPython向けのWebアプリケーションフレームワークです。

Streamlitを使うとフロントエンドの知識ゼロでもデータを可視化するためのWebアプリケーションを簡単に作れます。以下のような機能が特徴。

  • 変数を地の文に書くだけで、GUIに出力される(マジックコマンド)
  • st.radiost.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でボーダーを描画するオプションが追加されました。
それぞれデザイン的に使いやすいシーンがありそうですね。

borders.py
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)

border

Markdownのテキストの色としてStreamlitのtheme.primaryColorを参照

Streamlitでは以下のサンプルのようにMarkdownでテキストの色が指定できるのですが、こちらにテーマで指定した値を参照できるようになりました。

new_markdown_color.py
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)

こんな感じで表示されます。

theming

ちなみにtheme.primaryColorというのはStreamlitの右上のハンバーガーメニューから設定できるテーマの設定値の一つです。

theme.primaryColor

このテーマ、もっと色々な設定ができるように検討が進められているみたいですよ。

DataFrameの列の固定

st.dataframeおよびst.data_editorで列を固定できるようになりました。
横に長いDataFrameを表示するときに威力を発揮します!
DataFrameの内容があらかじめ分かっているときが特に使いやすいですが、内容が未知であってもユーザに固定する列を選択させることも考えられますね。
ちなみに、飛び飛びの列を固定するとそれらの列は左側に寄せて表示されます。

column_pinning.py
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),
    }
)

columns pinning

ターシャリ(tertiary)ボタン

st.buttonst.download_buttonst.link_buttonおよびst.form_submit_buttonの見た目をターシャリ(tertiary1)ボタンにするtertiaryオプションが追加されました。
非常に目立つ見た目のprimaryボタン、普通な見た目のsecondaryボタンに対してまったく目立たない見た目なのがターシャリボタンです。実際に試してみると、ボーダーのないボタンが表示されます。
普段は使わないんだけどいざというときにすぐアクセスしたいんだよね、という機能を割り当てるのに適しています。例えば編集や印刷などに切り替えるボタンとして使えるかも。

tertiary_buttons.py
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オプションが使える

st_tertiary_buttons.png

パス指定にpathlib.Pathを使用

ファイル名を指定するコンポーネントにpathlib.Pathをそのまま渡せるようになりました。今までは文字列にキャストする必要があったんですね。
数ファイルで完結する小規模なアプリだとあまり恩恵はないですが、ディレクトリ構造が複雑になってくると嬉しいですね。

pathlib_test.py
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)

pathlib

st.date_inputおよびst.time_inputの初期値をISO形式の文字列で指定

日付や時間を指定するとき、これまではdatetime型を指定していたのですが、ISO形式の文字列で指定できるようになりました。
また、Pandasから日付や時間を渡すときもキャストが不要になったので地味に便利に。

iso_time_initialization.py
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)

iso format

st.write_streamにAsync Generatorを渡せる

意欲的な機能ですね。
LLMが回答をちょっとづつ出力しているとき、例えばAsync Generatorとして受け取ることになりますが、これをそのままst.write_streamに渡すことができるようになりました。
st.write_streamに渡されたAsync Generatorは、内部で通常のGeneratorに変換されるので、並列処理的な意味では嬉しさはないのですが、インタフェースを変換する必要がなくなりましたね。

async_generator.py
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)

Async Generator

エラーメッセージの抑止設定

エラーメッセージが以下の4種類から選べるようになりました。

  • fullExceptionも含めてすべて表示します。
  • stacktraceExceptionの詳細を伏せます。ただ、スタックトレース(Traceback)は表示されるので、意味は薄いかも。コンソールにはすべてログが出力されています。
  • typeException名のみ表示されます。これだと内部構造や変数名が分かりにくくなりますね。
  • none:エラーが起こったことのみ表示されます。

内部構造や変数名、ファイルパスなどが悪意のある攻撃者にバレるとリスクがあるので、アプリをリリースするときはエラーメッセージを抑止するのがいいでしょう。

error_message_test.py
import streamlit as st

# helloというオブジェクトはないので必ずエラーが発生する
st.write(hello)

具体的な表示は順に以下のようになります。

error1

error2

error3

error4

設定は.streamlit/config.tomlに記載しましょう。

.streamlit/config.toml
[client]
showErrorDetails = "stacktrace"

st.line_chartにツールチップを表示

st.line_chartにマウスカーソルの位置にツールチップが表示されるようになったぞ!
ちょっとリッチなグラフモジュールだと元々対応していたのですが、一番お手軽なst.line_chartでも対応してくれたのは嬉しいですね。

new_line_chart_hover.py
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)

mouse hover

st.html_repr_html_()を呼び出せるか試行するように変更

_repr_html_()はそのオブジェクトをHTML形式でレンダリングする関数で、特にJupyter Notebookの世界でよく使われています。
この関数を持っているオブジェクトをst.htmlに渡すと_repr_html_()が返したHTMLを描画してくれるようになりました。

repr_html.py
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)

repr_html

Jupyter Notebookでは以下のような動作をします。

Juyter Notebook with Seaborn

なお、手元で試した範囲では、なんでもいい感じに表示してくれるという訳ではないようです。
_repr_html_()の出力に含まれているJavaScriptでグラフを描画する、みたいなものは上手く表示できませんでした。

Python 3.13のサポート

正式にPython 3.13をサポートするようになりました。
一方、Python 3.8がサポート対象外になっています。

サポート対象外というだけでしばらく前からPython 3.13でも動いていたのですが…試してみましょう。

new_python.py
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)

Python 3.13

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に対応していないため、今回紹介した機能は使えません。対応を心待ちにしましょう!

  1. 「三次」「3位の」みたいな意味

7
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?