156
Help us understand the problem. What are the problem?

More than 3 years have passed since last update.

posted at

updated at

[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 [モジュール名] 不要に取り込まれてしまったモジュールを取り除く
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
156
Help us understand the problem. What are the problem?