超軽量、超高速な配布用Python「embeddable python」

0.wybf0c7nin9.png

Pythonを独立したExeファイルとして書き出すPyInstallerの記事を以前書きました
しかし、この方法よりも実行がより高速、より軽量な方法がありました。
embeddable pythonと呼ばれるもので、Windows限定ではありますが、ダウンロードしてきた圧縮状態なら 7 MB解凍しても 14 MB程度のPython実行環境です。
もちろんこのままではパッケージを使えませんので、そのパッケージ導入方法も合わせて記載します。

公式
3. Windows で Python を使う — Python 3.6.4 ドキュメント

ダウンロード

Python Releases for Windows | Python.org

上記ページにあるWindows x86-64 embeddable zip fileをダウンロードして解凍。
もしくは、WindowsPowerShellで以下を実行。(wgetが使えるため楽だから)

PowerShell
# 作業用フォルダへ移動(適当なところでOK)
cd (適当なところ)
# PowerShellのwgetでファイルをダウンロード(ウィルス対策ソフトに注意)
wget "https://www.python.org/ftp/python/3.6.4/python-3.6.4-embed-amd64.zip" -O "epython_zip.zip"
# 解凍
Expand-Archive -Path epython_zip.zip -DestinationPath epython

0.igcn5ygx4xs.png

最小構成

pythonNN.dll、pythonNN.zip、vcruntime140.dllがあればPythonは動作します。
python.exeは、pythonNN.dllへ引数を送るだけの実行ファイルですので、独自C++に組み込むときは必要ありません。
(pythonNN.zipは圧縮ファイルですが、このままで機能しますので解凍してはいけません。)

今回は他の構成ファイルを使うのでそのままにしておいてください。

0.9x3kncj8xjq.PNG

PIP

pythonNN._pthファイルを修正。

そのままではget-pip.pyが使えないので、少し手直しをする必要があります。
ダウンロードしてきたファイルの中にあるpython36._pthというファイルをエディタで開いて、

python36._pth
# import site

を、

python36._pth
import site

とコメント解除する必要があります。
python 3.6 embed cannot get pip · Issue #7 · pypa/get-pip · GitHub

それから、https://bootstrap.pypa.io/get-pip.pyのファイルをダウンロードするか、ダウンロード処理をPowerShellで以下の通りに実行。

PowerShell
# 解凍したepythonの中身へ移動
cd epython
# そのままではwgetでダウンロードできないので、下記を実行(PowerShellではデフォルトでTLS1.2が非対応のため)
# 一時的にTLS1.2を有効にします(PowerShellを閉じると解除される)
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12;
# Wget
wget "https://bootstrap.pypa.io/get-pip.py" -O "get-pip.py"

コマンドプロンプトで作業

これでget-pip.pyが使えるのでインストール作業をしますが、
ここからはコマンドプロンプトで作業します。(PowerShellではモジュールエラーで実行出来なかった。)

0.0v0n1hrp4rv.PNG

要注意点

get-pipに限らず、ルート環境にpythonがインストールされているときはembeddable pythonなのかルート環境なのかを注意して実行すること。(作業フォルダにembeddable pythonがあるかどうか。もしくはpipインストール後にpython -m pip list等でパッケージの中身を見て確認。)

cmd
cd (embeddable pythonのフォルダ)
# 念の為、python.exeがあるディレクトリか確認。
dir
# リストの中にpython.exeがあれば、get-pipをインストール
python get-pip.py

これでpipが使えるようになりました。
ただし、

cmd
# NG
# pip install numpy
# OK
python -m pip install numpy

のようにpython -m pip install (パッケージ名)の書式でなければなりません。

試しにMatplotlibとWxPython

試しにMatplotlibを使ってグラフを書きつつ、wxPythonで描画してみましょう。

Package

以下のようにパッケージをインストール

cmd
python -m pip install numpy
python -m pip install matplotlib
python -m pip install wxpython

テストスクリプト

テストスクリプトは下記URLから引用。
main.pyという名前でpython.exeと同じ階層に保存してください。

スクリプト引用元
python - Embedding a matplotlib figure inside a WxPython panel - Stack Overflow

main.py
#! env python
# -*- coding: utf-8 -*-

from numpy import arange, sin, pi
import matplotlib
matplotlib.use('WXAgg')

from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
from matplotlib.figure import Figure

import wx

class CanvasPanel(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent)
        self.figure = Figure()
        self.axes = self.figure.add_subplot(111)
        self.canvas = FigureCanvas(self, -1, self.figure)
        self.sizer = wx.BoxSizer(wx.VERTICAL)
        self.sizer.Add(self.canvas, 1, wx.LEFT | wx.TOP | wx.GROW)
        self.SetSizer(self.sizer)
        self.Fit()

    def draw(self):
        t = arange(0.0, 3.0, 0.01)
        s = sin(2 * pi * t)
        self.axes.plot(t, s)


if __name__ == "__main__":
    app = wx.PySimpleApp()
    fr = wx.Frame(None, title='test')
    panel = CanvasPanel(fr)
    panel.draw()
    fr.Show()
    app.MainLoop()

バッチファイルを保存

同じ階層にバッチファイルをmain.cmdという名前で以下のように記載して保存。

main.cmd
# このファイルの位置を作業ディレクトリに
cd /d %~dp0
# main.pyを実行
python.exe main.py

main.cmdを実行するとグラフが描画される。

0.wyhsc17sc2.png

容量

ディレクトリの容量は 183 M程度になりました。
パッケージを含めると流石に容量が大きいですが、しかし、速度はPyInstallerより高速に動いている感じがします。

C++にembeddable pythonを埋め込む

別の記事として編集中。そのうち気が向いたときにでも書いていきます。

公式
1. 他のアプリケーションへの Python の埋め込み — Python 3.6.4 ドキュメント
Python/C API リファレンスマニュアル — Python 3.6.4 ドキュメント