user:
Streamlit アプリは、ユーザが何かするたびに再計算しますが、再計算せずに済ます箇所を指定しておくことはできますか?
assistant:
はい、Streamlit では「再計算(再実行)」を制御して、不要な再実行を防ぐ方法がいくつか用意されています。完全に再実行を止めることはできませんが、計算のキャッシュやステート管理を使って、必要な部分だけ効率よく処理することができます。
✅ 方法1:@st.cache_data
や @st.cache_resource
を使う
特定の処理結果をキャッシュして、同じ入力なら再実行せずキャッシュを使うようにできます。
import streamlit as st
@st.cache_data
def expensive_computation(x):
st.write("処理中…") # 再実行されたかどうか確認
return x ** 2
value = st.number_input("数値を入力", value=1)
result = expensive_computation(value)
st.write("結果:", result)
🔁 同じ数値を入力し続ける限り expensive_computation()
は再実行されません。
✅ 方法2:st.session_state
を使う
Streamlit はユーザ操作で全スクリプトを上から再実行するため、値を保持したい場合は st.session_state
を使います。
import streamlit as st
if "count" not in st.session_state:
st.session_state.count = 0
def increment():
st.session_state.count += 1
st.button("カウントアップ", on_click=increment)
st.write("現在のカウント:", st.session_state.count)
➡️ on_click
経由で関数を呼ぶことで、必要最小限の再実行に抑えることができます。
✅ 方法3:st.button
, st.form
, st.expander
などの構造を活用
Streamlit では、フォーム (st.form
) 内の入力が「Submit」されたときだけ再実行する構造が使えます。
with st.form("my_form"):
name = st.text_input("名前を入力")
submitted = st.form_submit_button("送信")
if submitted:
st.write(f"こんにちは、{name}さん!")
補足:再実行自体は止められないが「計算量の重い処理」は避けられる
Streamlit の設計上、何か変わると スクリプト全体を再実行します(例:ボタン、入力、選択肢などの変更)。
ただし、上記の方法を使えば、重い処理やデータロードなどを賢くスキップできます。
まとめ
方法 | 説明 |
---|---|
@st.cache_data / @st.cache_resource
|
重い処理の結果をキャッシュする |
st.session_state |
変数の値を保持して、必要なタイミングでのみ変更 |
st.form |
ユーザの「送信」をトリガーに再実行 |
on_click 引数 |
ボタンクリック時のみ関数実行 |
必要であれば、あなたのアプリに応じた最適な再計算抑制パターンを一緒に設計できますよ。お気軽にご相談ください。
user:
ありがとうございます。方法1を試したら、次のエラーが出ました。解決法はたぶん分かりますが、エラーの内容を詳しく説明していただけますか?
streamlit.runtime.caching.cache_errors.UnhashableParamError: Cannot hash argument 'columns' (of type pandas.core.indexes.base.Index) in ‘function’.
To address this, you can tell Streamlit not to hash this argument by adding a leading underscore to the argument's name in the function signature:
@st.cache_data
def function(_columns, ...):
...
assistant:
こちらこそ、詳細に共有いただきありがとうございます!
あなたが遭遇したエラー:
streamlit.runtime.caching.cache_errors.UnhashableParamError:
Cannot hash argument 'columns' (of type pandas.core.indexes.base.Index) in ‘function’.
このエラーは、Streamlit のキャッシュシステム(@st.cache_data
)が引数の「ハッシュ化(内容の識別)」に失敗したことを意味します。以下、丁寧に解説します。
🔍 なぜこのエラーが起きたのか?
Streamlit の @st.cache_data
は、関数の引数の内容を「ハッシュ化(要するに中身の識別)」して、同じ入力なら再実行しないようにしています。
しかし、一部のデータ型(特に pandas の Index や DataFrame)などは、「ハッシュ可能(hashable)」ではないため、ハッシュ化に失敗してエラーになります。
このエラーは、例えば以下のような関数をキャッシュしようとした場合に出ます:
import pandas as pd
import streamlit as st
@st.cache_data
def my_func(columns): # columns が Index 型
return list(columns)
df = pd.DataFrame({"a": [1, 2], "b": [3, 4]})
my_func(df.columns) # ← df.columns は pandas.Index 型
💡 解決策:Streamlit に「この引数はハッシュ対象外」と伝える
Streamlit は、関数の引数名の先頭にアンダースコア( _
)を付けると、その引数はハッシュ対象から除外します。
✅ 修正例:
@st.cache_data
def my_func(_columns): # ← _columns にすることでキャッシュ対象外に
return list(_columns)
df = pd.DataFrame({"a": [1, 2], "b": [3, 4]})
my_func(df.columns)
これでエラーは解消され、my_func()
はキャッシュされるようになります。_columns
はキャッシュに使われないので、キャッシュのキーには影響しません。
⚠️ 注意点:「キャッシュ対象外」にすることの意味
アンダースコアをつけてキャッシュ対象外にするということは:
- その引数が変わってもキャッシュは再実行されない(古い結果が返される)
- したがって、その引数が関数の結果に影響しないときだけ使うべき
もし columns
の中身が結果に影響するなら、次のような別の方法で対応するべきです:
✅ 別の方法:リストに変換して渡す
@st.cache_data
def my_func(columns_list): # list は hashable
return columns_list
df = pd.DataFrame({"a": [1, 2], "b": [3, 4]})
my_func(list(df.columns))
こうすれば、キャッシュキーとして適切に扱われます(list
はハッシュ可能)。
✅ まとめ
原因 | pandas の Index 型などが @st.cache_data のハッシュ対象にできない |
---|---|
解決方法1 | 引数名の先頭に _ をつけて「キャッシュ対象外」にする |
解決方法2 | 引数を list() や tuple() などの「ハッシュ可能な型」に変換する |
注意点 | キャッシュ対象外にした引数は、値が変わってもキャッシュが更新されない |
エラーの理由を納得いただけたら幸いです。もし columns
が重要な引数なら、型変換による対応をおすすめします。他のキャッシュや最適化のご相談も歓迎です。
user:
ありがとうございます。@st.cache_data と @st.cache_resource はどう違いますか?
assistant:
どういたしまして!
@st.cache_data
と @st.cache_resource
の違いは、**「何をキャッシュするか」**にあります。ざっくり言うと:
✅ @st.cache_data
→ 「データ」をキャッシュするのに使います。
- 対象:軽量なデータ処理、データフレーム、計算結果、APIレスポンスなど
- スコープ:入力引数ごとにキャッシュされる
- 再実行の条件:引数が変われば再実行される
- 典型例:Pandas データ読み込み、集計処理など
例:
@st.cache_data
def load_data(path):
return pd.read_csv(path)
✅ @st.cache_resource
→ 「リソース(外部接続や重いオブジェクト)」をキャッシュするのに使います。
- 対象:モデルの読み込み、大規模データの初期化、DB接続、APIクライアントなど
- スコープ:アプリ起動中は1回だけ実行(引数に関係なく)
- 再実行の条件:コードが変わらない限り再実行されない
- 典型例:LLM、DB接続、学習済モデルなどの読み込み
例:
@st.cache_resource
def load_model():
return SomeHeavyModel.load_from_disk("model_path")
📊 比較表
特徴 | @st.cache_data |
@st.cache_resource |
---|---|---|
主な用途 | データ、計算結果のキャッシュ | モデル、接続、外部リソースのキャッシュ |
再実行のトリガー | 引数が変わったとき | コードが変わったとき |
キャッシュの単位 | 引数に応じた複数バージョン | 1回のみの初期化(singleton 的) |
典型的な使い方 | CSV読込、集計処理 | モデル読込、DBコネクション |
✅ 実際どう使い分ければいいの?
- データ処理や計算 →
@st.cache_data
- 重い初期化(モデルや接続) →
@st.cache_resource
✨ヒント:
- データ処理で
st.cache_resource
を使うと、引数が変わっても結果が変わらず、意図しないキャッシュミスが起こることがあります。 - モデルなどで
st.cache_data
を使うと、毎回ロードし直して無駄に遅くなることがあります。
💡補足:従来の @st.cache
との違い
Streamlit 1.18 以降では、古い @st.cache
は非推奨で、以下のように用途ごとに分かれました:
-
@st.cache_data
(データ用) -
@st.cache_resource
(リソース用)
必要であれば、あなたのアプリの具体的なケースに合わせて「どっちを使うべきか」ご提案しますよ!
user:
ありがとうございました。今日はこの辺りで大丈夫です。ここまでの内容をブログにまとめたいので、タイトル案をいくつか提案していただけませんか?
assistant:
もちろんです!今日のStreamlitに関するキャッシュ・再実行制御の話をもとに、以下のようなブログタイトル案を提案します:
🔹 再実行の最適化にフォーカスしたタイトル
- 「Streamlitの再実行を止めたい?キャッシュとステートで効率化する方法」
- 「Streamlitで毎回再計算されて困る人へ:@st.cache_data と session_state の使い分け」
- 「Streamlitアプリ高速化の基本:再実行の仕組みとキャッシュの賢い使い方」
🔹 @st.cache_data
/ @st.cache_resource
の使い分けにフォーカス
- 「@st.cache_data と @st.cache_resource を使いこなす:Streamlit最適化ガイド」
- 「Streamlitキャッシュの正しい使い方:データ vs リソースのベストプラクティス」
- 「なぜその関数は毎回実行されるのか?Streamlitのキャッシュで解決!」
🔹 初学者にも優しい語り口のタイトル
- 「Streamlitの“毎回全部再計算”問題をどう防ぐ?今日からできる対策まとめ」
- 「Streamlit初心者がつまずく“再実行地獄”から抜け出す3つの工夫」
- 「Streamlitのキャッシュ戦略:最小限の再計算でサクサク動くアプリを作る」
ご希望のトーン(技術寄り/初学者寄り/SEO重視など)があれば、それに合わせて再提案もできますので、お気軽にどうぞ!
user:
「Streamlitの再実行を止めたい?session_state / @st.cache_data / @st.cache_resource の使い分け」で行きたいと思います。最後に、詳しいまとめを書いてください。
assistant:
素晴らしいタイトルです!
では、ブログ記事の最後にふさわしい詳しいまとめをご用意しました👇
📝 まとめ:Streamlitの再実行をどう制御するか?
Streamlit は「ユーザーの操作があるたびにスクリプトを上から再実行する」という設計が特徴ですが、毎回全処理が動くと非効率になります。そこで重要になるのが、再実行を制御する3つの基本機能です。
✅ 1. st.session_state
:状態保持の基本
- ユーザーの操作や関数呼び出しで変数を保持する方法。
- ボタンクリックで値を変更し、それ以外は再実行しないなど、意図的なトリガーの制御に使える。
- 初期化条件の管理や状態遷移の実装にも便利。
if "count" not in st.session_state:
st.session_state.count = 0
def increment():
st.session_state.count += 1
st.button("カウントアップ", on_click=increment)
✅ 2. @st.cache_data
:軽量なデータ・処理結果のキャッシュ
- Pandas の
read_csv
や軽い前処理関数など、入力に応じて変化する計算処理に最適。 - 同じ引数なら結果を再利用し、重い再計算を避けられる。
- ただし、非ハッシュ可能な引数(例:
pd.Index
)には注意。
@st.cache_data
def load_data(path):
return pd.read_csv(path)
✅ 3. @st.cache_resource
:モデルやDBなど重いリソースのキャッシュ
- 機械学習モデルや外部API接続、DB接続などの初期化コストが高いリソースを一度だけ読み込む用途。
- 引数に関係なく、アプリ実行中は一度だけ実行される。
- 基本的に「設定が変わらない前提の処理」に使う。
@st.cache_resource
def load_model():
return SomeModel.load("model.pt")
🔄 どれを使うべきか?
処理内容 | 使うべきもの |
---|---|
ユーザの操作状態管理 | st.session_state |
入力に応じた軽い処理 | @st.cache_data |
モデルや外部接続など | @st.cache_resource |
⚠️ 注意点:キャッシュの「効きすぎ」にも注意!
-
@st.cache_data
や@st.cache_resource
は、結果がキャッシュされている間は更新されないため、値が変わらないのに動作がおかしいように見えることもあります。 - その場合は
clear_cache()
や関数定義の変更などで再キャッシュを促す必要があります。
🎯 最後に
Streamlit の「毎回全再実行」という性質は、うまく使えば非常に直感的で強力ですが、計算量やユーザビリティを意識した制御が不可欠です。
今回紹介した st.session_state
、@st.cache_data
、@st.cache_resource
の3本柱を使いこなして、サクサク動く実用的なアプリを作っていきましょう!
必要に応じて、コードスニペットや応用例などを記事に追加するのもおすすめです!仕上げのサポートがあれば、いつでもどうぞ。
user:
ありがとうございました!また次回もよろしくお願いします!
assistant:
こちらこそ、ありがとうございました!
いつでもお気軽にご相談ください。次回も楽しみにしております 😊
良い執筆&開発を!✍️🚀