pythonだけでwebアプリを作る！streamlit！

# pythonだけでwebアプリを作る！streamlit！

この記事はIPFactory Advent Calender 2023 8日目の記事です。

https://qiita.com/advent-calendar/2023/ipfactory

## 記事の目的等
コンテストのためにstreamlitを使ったツールを作ったので、streamlitとはなにかを軽く説明したのち、他のstreamlitの記事になかったような要素とよく使った要素を紹介していこうと思います。


この記事はあくまで補足で使ってほしいので他の方が書かれた見ておくべきおすすめの記事を合間に貼っておきます。
とりあえず大体どんな要素があるか知りたいとき
https://qiita.com/not13/items/dcd8c12d64982dc0e819

## 優位点
エラーすら吐かないhtml,js,phpを使わなくていい！
(pythonなので)エラーがわかりやすい！
(pythonなので)いろいろなライブラリと併用できる！
```shell:shell
pip install streamlit
```
で簡単に導入できる

## 起動方法
基本shellから起動するのですが、streamlitを扱うファイルと同じフォルダに以下のようなbatファイルを作ることで1クリックで起動することができます。
おすすめです。

```bat:streamrit_test.bat
streamlit run --server.address localhost "streamlit_test.py"
cmd /k

rem "streamlit_test.py"には起動したいファイル名を入れる
```

また、コードを変更して保存した際は既に開いているstreamlitのページをリロードするだけで更新されるので起動しなおす必要がないです。
ただし複数タブの起動はできないと思います。

## できること
タイトル、本文

```py:streamrit_test.py
import streamlit as st

st.title('Streamlit_Test')

st.write('Display Image')
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3348724/38b63071-4ed5-bc27-aa7c-69f5bf0f2142.png)

入力フォームを作る

```py:streamrit_test.py
import streamlit as st

st.title('Streamlit_Test')

name = st.text_input('Enter your name')

st.write('Hello', name, "!")
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3348724/3678d493-0e6f-0153-ddb1-00489885dcf2.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3348724/750f72d6-4245-83fa-423b-38a2326a4032.png)

このようにとても簡単に要素を追加することができます。

## 本題

### いろいろなパラメータ

```py:streamrit_test.py
import streamlit as st


word = st.text_input(value="word", label="word",placeholder="input word")
number = st.number_input(value=1, label="number", placeholder=100, min_value=0, max_value=1000)

if st.button("submit"):
    st.write(f"word: {word}")
    st.write(f"number: {number}")
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3348724/2814833b-08db-2ea4-9c4b-370552ab5ffd.png)


- lavel
    - 左上に出るタイトル
- value
    - 初期値
    - numberはここに整数を入れることで整数のみを扱い、小数をいれることで小数を扱うことができるようになる
- placeholder
    - 入力欄が空のとき背景にうっすら出てるやつ
- max_value,min_value
    - 数字の上限値、下限値

その他使えるパラメータは公式ドキュメントを参照してください。

https://docs.streamlit.io/library/api-reference/widgets/st.text_input

### st.empty()で場所を確保する

次はMBSDCCでツールを作るときによく使っていた位置を先に決めておいてその内容を置換するやつです。
Javascriptでもこんなのがあった気がします。（がまともに勉強してないのでよくわかりません）

本来st.empty()は要素を初期化するために使うものですが、要素が存在しない場合変数宣言のような挙動をとります。

```py:streamrit_test.py
import streamlit as st

word = st.text_input(value="word", label="word")
number = st.number_input(value=1, label="number", placeholder=100)

wordPlace = st.empty()
numberPlace = st.empty()

if st.button("submit"):
    wordPlace.text(word)
    numberPlace.write(number)

if st.button("multiply"):
    wordPlace.text(word*number)
    numberPlace.write(number*number)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3348724/abd09caf-7461-fca2-7590-4b78c55a67c6.png)

↑ これはsubmitを押した状態

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3348724/cc130cea-d79e-c517-d82b-ef8badf26a9d.png)

↑ multiplyを押した場合

これはwordPlaceとnumberPlaceを先に定義し、場所を確保したうえで中身を書き換えています。
何も場所を指定しない場合、本来ならsubmitボタンよりも下に"*word*"や"*100*"は生成されるのですが、今回はinputとbuttonの間に場所を確保してあるので真ん中に表示されます。
また、multiplyボタンを押した際、wordPlaceは1行しかないので溢れています。
st.xxx_inputとst.empty()による場所指定も上から順番に入るので、先に場所を確保(して後述のsesson_stateで保存)すればinputより上に表示することもできます。

### コラムを作って縦に分割する

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3348724/e5367147-1fcd-ce10-2282-082fd2ae2f24.png)
↑ 決めた比を超えることはない

```py:streamrit_test.py
import streamlit as st

st.title("Streamlit Test")

number = st.number_input(label="number", value=1, placeholder="Enter a number")

form = st.empty()

with form:
    name_number_about = st.empty()
    col0, col1, col2 = name_number_about.columns((3, 2, 5))
    col0.write("I love this number!")
    col1.write(number)
    col2.write("Here's some textttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt")
```

コラムを作ってそれらに比を割り振ることで横に様々な要素を並べることができます
今回は **3:2:5** で割り振っています
col2のように長い文章でもコラムに決められた横幅を超えることはないです


## 注意点

### 実行されるたび状況がリセットされる
streamlit最大の問題がこれで、実行(ボタンを押すなど)されるたびに
**全てを１から読み込みなおします**
そのためどこかに値を保持しておく必要があります

```py:streamrit_test.py
import streamlit as st

st.title('Streamlit hatomato')

word = st.text_input('enter your favorite word')
number = st.number_input('enter your favorite number')
things = st.multiselect('enter your favorite things', ['apple', 'orange', 'banana'])

if st.button('submit'):
    st.write(word)
    st.write(number)
    st.write(things)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3348724/4b1e56c8-7d59-d726-20b3-99efb66ff971.png)
↑ これは値を入力してsubmitを押した直後

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3348724/de6aca3d-adb0-a7f8-4c48-4ac900f36c7e.png)
↑ 1文字消して更新しただけで先ほどの結果が消えてしまった

(見えないだけで値は持っている?…というか入力欄をもう一度見て値を入れ直しているのだと思う)

今はこれだけしか機能がないのでこの状態でも構いませんがこれがwebアプリを作ろうと思うとそうはいきません。

### st.sesson_state[]で値を保持する


もちろん対策がないわけがなくて、sesson_stateというものを使えば状況を保持することができます。
sesson_stateは値の更新が起ころうと、sesson_stateにある値を保持し続けます。

```py:streamrit_test.py
import streamlit as st

st.title('Streamlit hatomato')

word = st.text_input('enter your favorite word')
number = st.number_input('enter your favorite number')
things = st.multiselect('enter your favorite things', ['apple', 'orange', 'banana'])

if 'opened' not in st.session_state:
    st.session_state["opened"] = False

if st.button('submit'):
    if st.session_state["opened"] == False:
        st.session_state["opened"] = True 

if st.session_state["opened"] == True:
    st.write(word)
    st.write(number)
    st.write(things)
```

これは
1. openedという値がsesson_stateになければfalseを入れる
1. ボタンを押したときsession_state["opened"] が FalseならTrueにする
1. もしsession_state["opened"]がTrueならst.write()の3つを実行

という形になっていて値が変わるたびに全て実行しなおしていることに変わりはないのですが、見かけ上は常にword,number,thingsの結果が見えています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3348724/cf72d7f3-3192-6052-8675-8ed351e7d117.png)
↑ 1文字消しても結果が表示され続けるようになった

他の方が書いた入力フォームとsesson_stateについての記事

https://qiita.com/nockn/items/15e081b58e02a0878855

### ちょっと遅い

1から読み込みなおしていることから規模が大きくなると遅くなる
あとpythonゆえに遅い
悲しいね

## まとめ
streamlitは
- 簡単にwebアプリ作成を体験できる 
- 比較的簡単なかつ初学者が入りやすいpythonで書くことができる
- エラーをちゃんと出してくれる
- 起動がbatファイル１つでできる

いいライブラリだと思います。
ここでは紹介しませんでしたがNumpyやPandasを使ったグラフ等の表示による使われている気がします。
もしpythonでなにか作ってみたいと思ったらぜひ使ってみてください。