Streamlitではcache_data()やcache_resource()を使って関数の実行結果をキャッシュすることが可能です。
キャッシュ機能について
使用する場合の目的としてはごく一般的な目的。初回実行時の結果を保持しておくことで、2回目以降の処理を高速化することが可能です。(cache_resource()の場合はメモリリソースの節約効果も期待できるでしょう。)
実装方法としては、冒頭に挙げたcache_data()あるいはcache_resource()を使用。キャッシュする内容を返す関数をデコレートする形となります。
デコレートした関数とそれに対する引数の組み合わせ毎に結果がキャッシュされます。
アプリケーション起動後、関数・引数の組み合わせで初めて呼び出された場合はデコレートされた関数の処理を実行します。同2回目以降の呼び出し時には、(キャッシュアウトされていない限りは)処理は実行されずキャッシュされた結果が返されます。
st.cache_data()
扱うことができるのはSerializableなオブジェクト。
Serializableではないオブジェクトを扱おうとすると実行時にエラーが発生する。
キャッシュを返す場合、キャッシュされたオブジェクトのコピーが返される。
st.cache_resource()
cache_dataとは違い、Serializableではないオブジェクトも扱いが可能。
キャッシュを返す場合、キャッシュされたオブジェクトそのものが返される。
各関数のパラメータ
主なパラメータは以下の通り。
※バージョンは、1.49.1です。
| 名称 | 値 |
|---|---|
| ttl | キャッシュした値の生存期間。単位は秒。 |
| max_entries | キャッシュ可能なオブジェクト数の上限。 |
| show_spinner | bool値 or 文字列。 文字列を指定した場合、関数実行中に指定された文字列が表示される。 |
| show_time | 関数の実行時間を表示。0.1秒単位。 |
例1
import streamlit as st
import pandas as pd
from datetime import datetime
@st.cache_data
def initDf() :
st.info( f'Executed at {datetime.now()}' )
row = [
[ '茨城県', 36.341822, 140.446795, 2806, '#00000080' ],
[ '栃木県', 36.565735, 139.883561, 1885, '#00000080' ],
[ '群馬県', 36.3906941, 139.0604227, 1890, '#00000080' ],
[ '埼玉県', 35.857002, 139.648847, 7332, '#00000080' ],
[ '千葉県', 35.60506, 140.123306, 6251, '#00000080' ],
[ '東京都', 35.689507, 139.691728, 14178, '#00000080' ],
[ '神奈川県', 35.447728, 139.642545, 9225, '#00000080' ] ,
]
return pd.DataFrame( row , columns=['都道府県', '緯度','経度', '人口', 'color'] )
df = initDf()
st.dataframe( df )
st.info( f'Object ID ={id( df )}, Called at {datetime.now()}' )
結果は例えば以下の通り。
| 初回アクセス | 2回目アクセス(※別セッション) |
|---|---|
![]() |
![]() |
末尾のst.info()から出力されている時刻は異なっているため、これらは異なるタイミングで実行されたものであることがわかるかと思います。一方、initDf()内のst.info()から出力されている時刻は同一となっています。このことから、2回目アクセスではinitDf()が実行されているわけではなく、初回アクセス時のinitDf()の実行結果のキャッシュが利用されていることが分かります。
また、末尾のst.info()で出力しているDataFrameオブジェクトのIDが初回アクセスと2回目アクセスで異なっています。少なくとも2回目アクセスでは、キャッシュされたDataFrameオブジェクトそのものが利用されているわけではない(コピーが利用されている)ことが分かります。
今回はcache_data()を使って実装しましたが、cache_resource()で実装した場合は一番下に表示されているObject ID = に続く値が両画面で一致します。
例2
import streamlit as st
import time
from datetime import datetime
st.set_page_config(
layout = 'wide'
)
@st.cache_data( ttl=600, max_entries=5, show_spinner="Now Loading...", show_time=True )
def initVal( i:int ) -> int :
time.sleep(3)
result = 2**i
st.info( f'Called at {datetime.now()}, args={i}' )
return result
col1, col2 = st.columns(2)
with col1 :
for i in range(1,7) :
st.write( initVal(i) )
with col2 :
for i in range(6,0,-1) :
st.write( f'Called at {datetime.now()}, Cached Value = {initVal(i)}' )
右側の実行結果に着目します。
initValの引数が6~2の場合は、表示されている実行時刻は左側の同じ引数での実行時刻と一致しています。つまりキャッシュが利用されています。
一方で引数が1の場合はsleepが効いている、つまりinitValが再度実行されているのが分かります。これは、max_entries=5のためinitVal(6)を実行した時点でエントリーが一杯であり、最古のキャッシュであるinitVal(1)分をキャッシュアウト(してinitVal(6)の結果をキャッシュ)した結果であることを読み取ることができます。


