[Python] EelをつかってHTML/CSS/JavaScriptでGUIを構築

はじめに

  • PythonでGUIのアプリを作りたい。
  • TkinterやKiviなどは試したけど、凝ったレイアウトを作ろうとして挫折した。
  • GUIをHTML/CSS/JavaScriptのWEB技術で作りたい。でも、Electronを使うのは腰が重い。

そんなときピッタリなPythonのGUIライブラリがEelです。
Eelは、とてもシンプルなElectronライクなGUIライブラリで、NumpyやPandasなどデータ処理などに秀でているPythonと、D3.jsなど描画に優れているJavaScriptをつなぎ合わせたアプリを、いとも簡単に構築することができます。

以下、公式GitHubのReadmeに沿って、説明します。

  • 実行環境
    • Windows 10
    • Python3.6
    • Eel 0.9.7

インストール

pip install eelしましょう。

Hello World

ルートにmain.pyを作成し、WEB関係のファイルはwebディレクトリの下に置きます。

main.py
web/
  main.html
  css/
  js/

わずか3行のスクリプトを書き、

main.py
import eel
eel.init("web")
eel.start("main.html")

適当なHTMLを書いてpython main.pyと実行すると

main.html
<html>
<head>
    <meta charset="UTF-8">
    <title>Eel</title>
</head>
<body>
    Hello World!!
</body>
</html>

これだけでHelloWorldが完成です。
hello.png

PythonからJavaScript関数を呼ぶ

eel.jsを読み込んだうえで、Pythonから呼びたい関数をeel.expose(js_function)します。

main.html
<html>
<head>
    <meta charset="UTF-8">
    <title>Eel</title>
</head>
<body>
    <div id="target"></div>
    <script type="text/javascript" src="/eel.js"></script>
    <script type="text/javascript">
        eel.expose(js_function)
        function js_function(values) {
            var s = ""
            for (var i = 0; i < values.length; i++) { s += values[i] + "<br>" }
            document.getElementById("target").innerHTML = s
        }
    </script>
</body>
</html>

eel.js_function()exposeされたJavaScript関数を呼ぶことができます。

main.py
import eel
import numpy as np

eel.init("web")
eel.js_function(np.random.rand(4).tolist()) # JSON serializableでないとダメ
eel.start("main.html")

JavaScriptの関数の戻り値をPythonで取得

JavaScriptの関数の戻り値をPythonで取得するには、Callbackと同期待ちの2種類の方法があります。

Callbackを設定

1つは、JavaScript関数が終了された後に実行されるCallback関数を設定する方法です。

eel.expose(js_function);
function js_function(args) {
  return "hogehoge";
}
eel.js_function(args)(lambda val: print(val + " from JavaScript"))

同期待ち

もう1つは、空のカッコをつけて、JavaScript関数の終了を待つ方法です。eel.start()の前に呼ぶとフリーズしてしまいます。

val = eel.js_function(args)()

JavaScriptからPython関数を呼ぶ

JavaScriptから呼びたいPython関数を、@eel.exposeでデコレートします。

main.py
import eel

@eel.expose
def python_function(val):
    print(val + " from JavaScript")

eel.init("web")
eel.start("main.html")

eel.jsを読み込んだうえで、eel.python_function(args)でPython関数を呼ぶことができます。
JavaScriptの配列はPythonのlistに、連想配列はdictへと変換されます。

main.html
<html>
<head>
    <meta charset="UTF-8">
    <title>Eel</title>
</head>
<body>
    <script type="text/javascript" src="/eel.js"></script>
    <script type="text/javascript">
        eel.python_function("aa")
    </script>
</body>
</html>

Pythonの関数の戻り値をJavaScriptで取得

async await を使います。

@eel.expose
def python_function2():
    return "Hello"
async function run() {
    let val = await eel.python_function2()();
    console.log(val + " from Python")
}
run();

起動時のオプション設定

web_app_options = {
    "mode": "chrome-app",  # chromeのアプリケーションモードで起動
                           # "chrome" とすると、通常のchromeで起動
    "port": 8080,
    "chromeFlags": [
            "--start-fullscreen",  # フルスクリーンで起動 他のChromeが起動していると機能しない?
            # "--window-size=800,600",
            # "--window-position=0,0",
    ]
}
eel.start("main.html", options=web_app_options)

マルチスレッド

eel.sleep()や、eel.spawn()を使うことで、簡単にマルチスレッド化できます。下の例は、

  • WEBサーバをホストするスレッド
  • my_other_thread
  • メインスレッド

の3つが走るスクリプトです。

import eel
eel.init("web")
def my_other_thread():
    while True:
        print("I'm a thread")
        eel.sleep(1.0) # time.sleep()でなくて、eel.sleep()を使うこと
eel.spawn(my_other_thread)
eel.start("main.html", block=False) # block=Falseとしないと、ここで止まってしまう
while True:
    print("I'm a main loop")
    eel.sleep(1.0)

動作サンプル ( Smoothie によるリアルタイムなグラフ)

Pythonでグラフといえばmatplotlibですが、ここでは、JavaScriptのライブラリSmoothie Chartsを使ってリアルタイムなグラフを描画してみます。

main.py
import random
import time
import eel

eel.init("web")
web_app_options = {"chromeFlags": ["--window-size=420,200"]}
eel.start("main.html", options=web_app_options, block=False)

while True:
    eel.push_data([
        {"time": time.time() * 1000, "value": random.random()}, # JSのgetTime()と同じにするためミリ秒に直す
    ])
    eel.sleep(0.2)
main.html
<html>
<head>
    <meta charset="UTF-8">
    <title>Chart</title>
</head>
<body>
    <div id="parent" style="height: 100%;">
        <canvas id="mycanvas"></canvas>
    </div>
    <script type="text/javascript" src="/eel.js"></script>
    <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/smoothie/1.34.0/smoothie.min.js"></script>
    <script type="text/javascript">
        // canvasの高さと幅を画面に合わせる
        var parent = document.getElementById("parent")
        var canvas = document.getElementById("mycanvas")
        canvas.width = parent.clientWidth
        canvas.height = parent.clientHeight

        var smoothie = new SmoothieChart();
        smoothie.streamTo(canvas);
        var series = new TimeSeries();
        smoothie.addTimeSeries(series);

        eel.expose(push_data)
        function push_data(values) {
            for (var i = 0; i < values.length; i++) {
                series.append(values[i]["time"], values[i]["value"]);
            }
        }
    </script>
</body>
</html>

chart.png

バイナリ化

  1. pip install pyinstallerをして、PyInstallerをインストールする
  2. main.pyのディレクトリでpython -m eel main.py webと実行すると、必要な引数を加えてPyInstallerが実行されます
  3. すると、distディレクトリ以下に実行ファイルが出来上がります
  • PyInstallerのオプションを引数に加えることもできます
    • --onefile ひとつのファイルにまとめる
    • --noconsole コンソールを表示させない
    • --exclude [モジュール名] 不要に取り込まれてしまったモジュールを取り除く
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.