♾️

Reactpyとか言う面白いライブラリ

2023/06/11に公開2

こんにちは。だだっこぱんだです。
今まではフロントエンドな人間のつもりでしたがいつの間にか Most used languages の1位がPythonで50%になっていてあれーってなっています。
https://github.com/ddPn08

今回はpythonで面白いライブラリを見つけたので紹介していきます。

Pythonとフロントエンド

「pythonでフロントエンドする必要ある?」って思う方がいるかもしれません。
割と需要あります。
今ある中ではGradio, Streamlit が有名ですね。
これらは stable-diffusion-webui で使われたり、AI関係の技術のデモでは毎度お馴染みレベルで使われます。

Gradioのデメリット

  • 拡張性が低い
  • バグが多い
  • システムがわかりづらい(フロントエンドをやっている人間からすると)

Gradioはクラス名を変えられなかったり、reactiveじゃなかったりして若干使いづらいです。
バグも多いので時々躓きます。

Streamlitのデメリット

  • アクセス毎にサーバー側のプロセスが増える

これは本当に致命的で、二つのタブでアプリを開いたりするとサーバー側のプロセスが増えます。
モデルとかをロードする処理を入れている場合VRAMを2倍消費します。
つらいです;

Reactpyとは

pythonでreactのようにフロントエンドを書けるライブラリです。

https://reactpy.dev

こんな感じで、ほぼほぼ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ライブラリを作ってみたりしています。

https://github.com/ddPn08/dukkha

全てはサーバーサイドで実行

onClickuseEffectなどの関数は全てサーバー側で実行されます。

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

TFTF

深く見てないのですが、

reactのようにuseStateが使えます。

サーバサイドでステートが持てるということなのでしょうか?Next.js的にはクライアント・コンポーネント'use client'になりますが、reactpyもフックを使うとクライアント・コンポーネントとして動くということでしょうかね。もしかするとシングル・インスタンス前提のステートフル・セッションかもしれませんね。

だだっこぱんだだだっこぱんだ

そうですね、自分もまだがっつり触れてないのですが、stateの値はサーバー側で管理されていて共通化していると思います。この辺りも自分の中の需要にばっちり合ってました。
また、streamlit -> ステートレス, reactpy -> ステートフル
みたいな使い分けができるかもしれないです。