Streamlit 導入一日目から爆速開発をするための(オレオレ)デザインパターン

Streamlitにべた惚れ中です。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">一日弱でShinyアプリのstreamlitへの移植が完了した。いや、これ最強。</p>&mdash; Taiki Komoda (@komde) <a href="https://twitter.com/komde/status/1673983717512204290?ref_src=twsrc%5Etfw">June 28, 2023</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

昔書いたShinyアプリを導入一日目で移植できて感動したものの、独自のクセに気づくのに3日ほどかかってしまったので、ちょっと複雑なことをやろうとする前にご覧いただければ幸いです。

## 結論

- 「ここだけ更新されれば効率的なのに」みたいなことは気にせず、「ああ、毎回実行されるのね」という気分でつらつら書くべし
- 決まったことをst.session_stateに追加するべし！
- st.stop()を効果的に使うべし！！
- レイアウトはMarkdownレベルだと思うべし！！！
- streamlitに組み込む前のユニット関数の実験はJupyterNotebook形式でやって残しておくと便利！

## 「ここだけ更新されれば効率的なのに」みたいなことは気にせず、「ああ、毎回実行されるのね」という気分でつらつら書くべし

Event/Callback/Reactivityみたいな概念がそもそも無いです。うまくクラスとかモジュールにまとめたり、MVCだとかMVPだとかMVVMだとかをしたくなっても我慢して、ひたすらスクリプトに沿って実行されることだけ考えましょう。

なので、レイアウトの記述順というのが重要になります。普通のフレームワークだとUIとイベント管理は独立しているのであまり気にする必要が無いのですが、どの順番で書くかを気にする必要があります。

例えばサイドバーにデータの取得条件を選択するウィジェットを配置しメインにその結果を表示する場合は、先にサイドバーを記述すると不要なifを削減することができます。記述順が入れ子になる場合はst.container()などを使用し、一旦場所を確保しましょう。

## 決まったことをst.session_stateに追加するべし！

APIなどにログインが必要な場合、ログイン画面→データ表示画面への遷移をしたいことがあるかと思います。この場合には以下のような対応をします。

```
if login not in st.session_state:
    #ログイン画面
    ...
    #ログインできたら
    st.session_state.login = "done" #何かをセットすれば動きます
    ...
else:
    #ログイン完了後の処理
    ...
```



## st.stop()を効果的に使うべし！！

さて、ちょいちょいやりたいことを追加していくと、ログインできていないとか、データフレームがまだ作られていないとか、値が定義されていないとかのチェックなどで自然とこんなイメージのコードになっていったりします。

```
if ログインできたら:
  ...
  with ... :
    ...
    if データが格納されたら:
      ...
      with ...:
        ...
    else ... :
```

もちろんこれで動作するのですが、インデントが深くなっていくというのは精神衛生上良くないですよね。そういう時は **st.stop()** で処理をぶった斬ると良いです。 **st.stop()** は以後の処理を行わない、というメソッドです。うまく利用できるとこんな感じにインデントを浅くすることができます。

```
if 'login' not in st.session_state:
    st.stop()

with ... :
    if 'df' not in st.session_state:
        st.warning('Please fetch the data')
        st.stop()
    with ...:
```

表示が真っ白でユーザーが心配になりそうな場合は st.warning() などで知らせてあげると親切ですね！

## レイアウトはMarkdownレベルだと思うべし！！！

ネットの記事で「細かいレイアウトはつらい」という記述をよく見たのですが、心の奥底で「でも何とかなるでしょ？」と思ってしまうものです。もちろん力技で何とかすることはできるのですが、streamlitの強さを最大限活かすには、可能な限り最小限の記述で済ませましょう。コツは「レイアウトしていく」というより、 **Markdownを記述している気分** で書いていくと良いです。

## streamlitに組み込む前のユニット関数の実験はJupyterNotebook形式でやって残しておくと便利！

これも一週間ぐらいいろんな手法を試してこのやり方に行きつきました。
Streamlitはホットリロード（サーバーを立ち上げ直さなくてもコードが適用）されるので、演算や表示を変更したら画面へリアルタイムで反映され確認できます。なのでアルゴリズム実験をこの方法でできて便利だなーと思って最初はこれでやっていたのですが、後日やっぱりこの方法だと推敲の軌跡にアクセスできなく、Streamlitのコードのコメントをたどったりデータをもう一度喰らわせたりする必要が出てきてしまうなと気づきました。

なので自分は別フォルダにユニットテスト用のJupyterNotebook形式(.ipynb)を配置し、同じモジュール読み込んで結果を残しておく方式にしました。やっぱりJupyterNotebook形式は後で考えを見返すには最適ですよね。VSCodeだと.py/.ipynbをいったりきたりしやすいのと、CSVに吐き出して軽量なRainbow CSV extensionでリアルタイムにデータの反映を確認することで、ここらへんの効率・品質をかなり上げることができるようになりました。

![Screenshot 2023-07-07 10.49.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103775/fbe870fb-1bfb-d64f-866b-c164b96a8120.png)


## 結論（再掲）

ということで、結論の再掲です。皆さんもぜひ爆速開発楽しんでください！

- 「ここだけ更新されれば効率的なのに」みたいなことは気にせず、「ああ、毎回実行されるのね」という気分でつらつら書くべし
- 決まったことをst.session_stateに追加するべし！
- st.stop()を効果的に使うべし！！
- レイアウトはMarkdownレベルだと思うべし！！！
- streamlitに組み込む前のユニット関数の実験はJupyterNotebook形式でやって残しておくと便利！

## streamlitを使い始めた背景

おまけのお話です。

仕事で使う分析環境を整理していて、まずはIDEをVSCodeに統一しました。ちなみにjupyterもこのタイミングでjupyterlabからVSCodeに乗り換えました。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">RstudioもVSCode+extensionで乗り換え可能そうだったので長らくサーバーのメモリの肥やしとなっていたRstudio Serverを引退させた。毎回RとPythonで分析するときにIDEの使い方思い出す時間がかかっていてモヤモヤしていたんだけど、これでスッキリ〜</p>&mdash; Taiki Komoda (@komde) <a href="https://twitter.com/komde/status/1670725307786227712?ref_src=twsrc%5Etfw">June 19, 2023</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

そろそろRを引退させたかったのですが、Shinyを何とかしなくてはいけないところが足を引っ張っていました。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">ちなみにShiny for Pythonめっちゃ気になる。自分が近年R使うケースは過去のShinyアプリをメンテするためだけで、新規は全てpythonに移行済みなのだけど、Shiny移行できたらPythonに一本化できるな。でも移行大変そうだな、、。dashに移植するより全然楽そうだけど、、。</p>&mdash; Taiki Komoda (@komde) <a href="https://twitter.com/komde/status/1670726572100423680?ref_src=twsrc%5Etfw">June 19, 2023</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

試しにShiny for PythonでUIだけ移植を試みたものの、、

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">過去のRで書いたShinyアプリをShiny for Pythonに移し替えようとしてみたけれども、ちょっとUI作ってみてdashで書くのとあまり工数的には差がない気がしてきた、、。そうなると単純にエコシステムとかユーザー数で決めた方が良いなぁ。</p>&mdash; Taiki Komoda (@komde) <a href="https://twitter.com/komde/status/1673706850372681728?ref_src=twsrc%5Etfw">June 27, 2023</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

Pythonではdashのほうがこなれているからやっぱりdashにするか〜と思い始めていて、良い評判だけ聞いていたstreamlitを試しに使ってみたところ、、

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">Shinyとかdashとか言ってたけど、念のためstreamlitで作ってみたらあっさり画面遷移含めてUI完成してしまったので、もうこれでいいかになってる💦。遷移が複雑とか大規模とかだと辛いかもしれないけど普通にvisualizationするだけなら最適解かも。デコレータって違和感がどうしても、、。</p>&mdash; Taiki Komoda (@komde) <a href="https://twitter.com/komde/status/1673858772194963463?ref_src=twsrc%5Etfw">June 28, 2023</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

結局その日みっちり作業してみたらロジック部分まで移植が完了。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">一日弱でShinyアプリのstreamlitへの移植が完了した。いや、これ最強。</p>&mdash; Taiki Komoda (@komde) <a href="https://twitter.com/komde/status/1673983717512204290?ref_src=twsrc%5Etfw">June 28, 2023</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

で、実はやりたいことは毎度秒速で実装できていたのですが、一番時間を使ったのがデザインパターンみたいなところでした。デザインパターンの記事を見なかったので書いてみた次第です。