Streamlitでリストの初期化を防ぐ

# はじめに
備忘録です。
何か他にもいい方法があったら教えて下さい。

# 本題
Streamlitを使用すると、簡単に描画アプリを作成することができます。
また、Rerunボタンを押すことで、すぐにソースコードの変更を反映できます
![スクリーンショット 2021-01-18 13.39.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/324574/0873c211-3951-9fe2-d49a-c2f0c70c5a85.png)

ここで問題が発生しました。
例えば以下のようにinputをリストで保持したい場合

```python
import streamlit as st

lst = []
input = st.text_input('何か入力して下さい')
lst.append(input)
st.table(lst)
```
appの出力はこのようになります。
![スクリーンショット 2021-01-18 13.50.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/324574/7d6527b3-e80f-5a95-edaa-ac37bbf5d0ed.png)
ここで何かを入力してみます。
![スクリーンショット 2021-01-18 13.52.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/324574/cf6b2edf-594a-e4ef-bada-6355aeb6f849.png)
aaaが反映されたことがわかります。
次に新しくbbbを追加してみます。
![スクリーンショット 2021-01-18 13.53.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/324574/f8232050-92fa-00ae-e9a7-0bbf889c7335.png)
bbbは追加されましたが、すでにあったaaaは反映されていませんでした。

なのでコードを修正してみました。チェックボックスがチェックされていると入力が止まるような
感じにしてみました。

```python
lst = []
i = 0
while True:
    input = st.text_input('何か入力して下さい', key=str(i))
    lst.append(input)
    i += 1
    if st.checkbox('stop'):
        break
st.table(lst)
```
出力は以下のようになりました。
![スクリーンショット 2021-01-18 13.59.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/324574/8a114556-c034-5fa9-abb9-af784304eebb.png)

streamlitで入力をループにするにはよくないっぽいです。(できたとしても入力欄が無限に出力されそう)

#原因
原因としてはStreamlitでRerunを実行すると、全てのデータをロードしてしまうからでした。
Rerunはボタンを押さなくても、入力欄での追加・変更がされるだけでも自動的に行われます。
そこでStreamlitのcache機能を使用します。
# Cacheとは
cacheはStreamlitのデコレーターです。
関数の前に@st.cacheをつけることで使用することができます。
簡単に言うとcacheを利用できるので、一度使った関数は引数が変わらないと変更が行われない感じです。
(変更される条件は他にも複数存在します。詳しくは[ドキュメント](https://docs.streamlit.io/en/stable/caching.html)を参考にしてみて下さい。)

# 改善
それではcacheを使用してコードを変更してみます。

```python
@st.cache(allow_output_mutation=True)
def cache_lst():
    lst = []
    return lst

lst = cache_lst()
input = st.text_input('何か入力して下さい')
if st.checkbox('clear'):
    caching.clear_cache()
    lst = cache_lst()
elif input:
    lst.append(input)
st.table(lst)

```
すると、以下のようにリストに値が保持できていることがわかります。
![スクリーンショット 2021-01-18 14.58.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/324574/68f6fd59-ebd8-fada-df3c-b2f5c1ca479d.png)

また、clearのチェックボックスをチェックするとリストを初期化することができます。
これを使うことで、要素の削除や変更を行うことができます。
また、このときリストはimmutableなので、要素の変更を行う際に代入をする形をとると変更が反映されません。

```python
@st.cache(allow_output_mutation=True)
def cache_lst():
    lst = []
    return lst

lst = cache_lst()
input = st.text_input('何か入力して下さい')
if st.checkbox('clear'):
    caching.clear_cache()
    lst = cache_lst()
elif input:
    lst.append(input)

if st.checkbox('delete'):
    delete = st.selectbox('削除する要素を選択して下さい', options=lst)
    if st.button('Delete'):
        lst.remove(delete)
        st.success(f'Delete : {delete}')

if st.checkbox('change'):
    change_from = st.selectbox('変更する要素を選択して下さい', options=lst)
    change_index = lst.index(change_from)
    change_to = st.text_input('何に変更しますか')
    if st.button('Change'):
        lst.remove(change_from)
        lst.insert(change_index, change_to)
        st.success(f'Change {change_from} to {change_to}')
st.table(lst)
```
![スクリーンショット 2021-01-18 15.17.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/324574/c1d86a48-3ffe-cb16-690d-163667e1b462.png)
削除した時
![スクリーンショット 2021-01-18 15.18.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/324574/cc0eb682-2691-6ee5-3a50-fd05405a1643.png)
変更した時
![スクリーンショット 2021-01-18 15.19.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/324574/462c8ed1-ebc0-ae0f-ccb0-feb87b5659a6.png)
**※削除・変更・追加をする際はinput欄に文字が入っていると勝手に追加されることがあるので注意して下さい**
# 参考
[Improve app performance](https://docs.streamlit.io/en/stable/caching.html)
[Streamlit入門](https://www.logopt.com/2020/08/18/streamlit%E5%85%A5%E9%96%80/)