Reactpyとか言う面白いライブラリ
こんにちは。だだっこぱんだです。
今まではフロントエンドな人間のつもりでしたがいつの間にか Most used languages
の1位がPythonで50%になっていてあれーってなっています。
今回はpythonで面白いライブラリを見つけたので紹介していきます。
Pythonとフロントエンド
「pythonでフロントエンドする必要ある?」って思う方がいるかもしれません。
割と需要あります。
今ある中ではGradio
, Streamlit
が有名ですね。
これらは stable-diffusion-webui
で使われたり、AI関係の技術のデモでは毎度お馴染みレベルで使われます。
Gradioのデメリット
- 拡張性が低い
- バグが多い
- システムがわかりづらい(フロントエンドをやっている人間からすると)
Gradioはクラス名を変えられなかったり、reactiveじゃなかったりして若干使いづらいです。
バグも多いので時々躓きます。
Streamlitのデメリット
- アクセス毎にサーバー側のプロセスが増える
これは本当に致命的で、二つのタブでアプリを開いたりするとサーバー側のプロセスが増えます。
モデルとかをロードする処理を入れている場合VRAMを2倍消費します。
つらいです;
Reactpyとは
pythonでreactのようにフロントエンドを書けるライブラリです。
こんな感じで、ほぼほぼreactみたいにフロントエンドを書けます。
from reactpy import component, html, run
@component
def App():
return html.h1("Hello, world!")
run(App)
reactpyの良い点
拡張性が高い
ほぼほぼhtmlの全てをいじれるのでとても拡張性が高いです。TailwindCSS
と組み合わせたり(検証済み)、Material-UI
などのUIライブラリなども使えるらしい(未検証)
https://reactpy.dev/docs/guides/escape-hatches/javascript-components.html
実際にいま、TailwindCSS
をつかってこういった自分用のUIライブラリを作ってみたりしています。
全てはサーバーサイドで実行
onClick
やuseEffect
などの関数は全てサーバー側で実行されます。
from reactpy import component, html, run
@component
def Button():
def handle_event(event):
print(event)
return html.button({"on_click": handle_event}, "Click me!")
run(Button)
こういったコードの際、handle_event
関数はサーバー側で実行され、event
の中身がログにプリントされます。
なので例えば、推論の処理を直接呼び出して返り値を表示することができたり...
from reactpy import component, html, run
from llm import pipeline
@component
def Button():
prompt, set_prompt = hooks.use_state("")
result, set_result = hooks.use_state("")
def handle_event(event):
set_result(pipeline(prompt=prompt))
return html.div(
html.input({"on_change": lambda event: set_prompt(event["target"]["value"])}, prompt)
html.button({"on_click": handle_event}, "Infer"),
html.input({"disabled": True}, result)
)
run(Button)
そして、Streamlitのようにサーバープロセスが増えたりはしません。(個人的に一番嬉しい。)
reactiveである
reactのようにuseState
が使えます。
from reactpy import component, hooks, html, run
@component
def SyncedInputs():
value, set_value = hooks.use_state("")
return html.p(
Input("First input", value, set_value),
Input("Second input", value, set_value),
)
@component
def Input(label, value, set_value):
def handle_change(event):
set_value(event["target"]["value"])
return html.label(
label + " ", html.input({"value": value, "on_change": handle_change})
)
run(SyncedInputs)
streamlitと同じようにwebsocketで通信してインタラクションのたびにサーバーにイベントが送られます。
そして、サーバー側で再レンダリングが走るとその情報がフロント側に送られてきます。
Reactpyを使う
超絶ざっくり解説します。
インストール
今回はfastapi
を使っていきます。他にも色々なバックエンドをサポートしているので詳しくはドキュメントをご覧ください。
https://reactpy.dev/docs/guides/getting-started/running-reactpy.html#running-reactpy-in-production
pip install reactpy[fastapi] fastapi
使い方
from fastapi import FastAPI
from uvicorn import run
from reactpy import component, html
from reactpy.backend.fastapi import configure, Options
def Head():
return (
html.link(
{
"href": "https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap",
"rel": "stylesheet",
}
),
html.script({"src": "https://cdn.tailwindcss.com"}, ""),
)
@component
def Root():
return html._(
html.h1(
"Hello, world!",
),
html.p(
"Hello, world!",
),
)
app = FastAPI()
configure(
app,
Root,
Options(head=Head()),
)
if __name__ == "__main__":
run(app)
こんな感じです。
コンポーネントの定義にはcomponent
デコレータを使います。
html._
はフラグメントです。
このRoot
コンポーネントはReact
の場合こんな感じになります
function Root() {
return (
<>
<h1>Hello, world!</h1>
<p>Hello, world!</p>
</>
)
}
Head
関数はhtmlの<head>
に入れるものを返します。
注意すべきポイントは、Head
は普通の関数でリストを返すようにしなければなりません。
そうしたら、fastapi
のインスタンスを作成し、configure
関数でいろいろして、最後にuvicornで起動するだけです。
以上!ざっくり解説でした。
Discussion
深く見てないのですが、
サーバサイドでステートが持てるということなのでしょうか?Next.js的にはクライアント・コンポーネント'use client'になりますが、reactpyもフックを使うとクライアント・コンポーネントとして動くということでしょうかね。もしかするとシングル・インスタンス前提のステートフル・セッションかもしれませんね。
そうですね、自分もまだがっつり触れてないのですが、stateの値はサーバー側で管理されていて共通化していると思います。この辺りも自分の中の需要にばっちり合ってました。
また、streamlit -> ステートレス, reactpy -> ステートフル
みたいな使い分けができるかもしれないです。