SyntaxHighlighter

2018年11月24日土曜日

1バイナリの Python と exepy

1バイナリの Python と exepy

1バイナリにした Stackless Python の更新もそろそろ終わりなので、その中に含まれている exepy ライブラリについてまとめておこうと思います。

1バイナリの Python の仕組み

この Python は、 外部 DLL や *.py ファイルが不要になっています。
DLL については、ビルド時に tcl/tk, OpenSSL などを含め、静的リンクすることで解決しています。
*.py ファイルについては、 embeddedimport というモジュールを新規に開発することで対応しています。

これらについては Single Binary 版の Stackless Python 3.6.4Single Binary StacklessPython 3.6.4 L8 リリースに簡単にまとめてあります。

exepy の仕組み

2.0.0 以降の exepy では、以下のようにして exe を作ることができます。

1
python.exe -m exepy create sample.exe input.py

上記で作られた exe は、一時フォルダにファイルを展開したりすることなく、単一の exe ファイルだけで input.py の処理を実行することができます。

さて、このとき、 sample.exe は python.exe とほぼ同じバイナリになっています。
唯一の違いは、 input.py に相当する内容が sample.exe のリソースとして組み込まれているところです。

embeddedimport は、実行している exe ファイル (sample.exe) のリソースに特定の ID のリソースが含まれている場合、その内容から *.py のデータをデコードしロードします。
つまり、 import input が実行された場合、 embeddedimport は以下の順で *.py を探します。

  1. 現在実行中の exe ファイルのリソース内
  2. exe 内に静的に持っている *.py のデータ (具体的には embeddedimport_data.c の内容)
そのため、元々の python.exe にリソースを埋め込むだけで、 *.py を含んだ exe ファイルを作ることができます。

exepy のサンプル

いくつかサンプルを示しておきます。

複数ファイルからなるサンプル

main.py と sub.py があり、 main.py が sub.py を import しているとします。

1
2
3
4
5
6
# main.py
 
import sub
 
sub.func()
print("main")
1
2
3
4
# sub.py
 
def func():
    print("sub.func")

上記のファイルを下記のように exe 化します。

1
> python.exe -m exepy create sample.exe main.py sub.py

上記で作られた sample.exe の実行は、実質的に下のコマンドを実行したときとほぼ同等の動きになります。

1
2
3
4
> python.exe -m main
sub.func
main
>

画像ファイルなどの抱き込み

Single Binary StacklessPython 3.6.6 L16 に含まれる exepy 2.1.0 では、画像ファイルなどの抱き込みができるようになっています。
例えば、 picture.png を tkinter で表示する例を考えます。

以下のような main.py を用意します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# main.py
 
import tkinter as tk
import os
 
root = tk.Tk()
 
path = os.path.join(os.path.dirname(__file__), "picture.png")
img_data = __loader__.get_data(path)
img = tk.PhotoImage(data=img_data)
 
label = tk.Label(image=img)
label.pack()
root.mainloop()

こちらを以下のコマンドで exe 化します。

1
> python.exe -m exepy create sample.exe main.py picture.png

これで picture.png も組み込まれた exe ができあがります。

__loader__.get_data(...) で、通常のファイルのようにデータを取得することができます。
あとはこれを用いて tkinter の Label, Widget などで表示すればよいです。

今回の変更で embeddedimporter が get_data をサポートしました。
これにより、任意のバイナリデータを組み込めるようになっています。

あとは、アイコンの変更機能ぐらいを追加して開発も終了です。