コミュニティ

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

0.wybf0c7nin9.png

追記:18-10-31 パッケージのインストールエラーに対応する方法を追記

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より高速に動いている感じがします。

エラー対応

pipでエラー

python -m pip install パッケージ名

上記のようにパッケージを追加しようとしたときエラーが表示されることがあります。
このエラーの原因は、zipファイル内のlib2to3というパッケージの中身が展開出来ないことによるもののようです。
対応方法としては、
1.同封の「PythonNN.zip」を失敗した時の為にバックアップ、コピーしておく。
2.「PythonNN.zip」をフォルダとして展開。
3.「PythonNN」というフォルダ名から「PythonNN.zip」に変更。

“python setup.py egg_info” failed with error code 1
https://stackoverflow.com/questions/42962765/embedded-python-3-5-python-setup-py-egg-info-failed-with-error-code-1

ModuleNotFoundError: No module named XXX

同じ階層にpyファイルがあり、それをimportしようとしてエラーが発生した場合、その発生箇所よりも前に、以下のスクリプトを追加する。

sys.path.append(os.path.dirname(os.path.abspath(sys.argv[0])))

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

mm_sys
deepoculus
ツカザキ病院眼科のAIチームです。医療AI(特に眼科領域)の開発を行うスタートアップ
https://www.tsukazaki-hp.jp/care/ophthalmology/ai
ユーザー登録して、Qiitaをもっと便利に使ってみませんか。
  1. あなたにマッチした記事をお届けします
    ユーザーやタグをフォローすることで、あなたが興味を持つ技術分野の情報をまとめてキャッチアップできます
  2. 便利な情報をあとで効率的に読み返せます
    気に入った記事を「ストック」することで、あとからすぐに検索できます
コメント
(編集済み)

私の環境では、python -m pip installの際に ModuleNotFoundError: No module named 'pip' というエラーが発生しました。この場合は、pythonXX._pthファイルに ./Lib/site-packages と追記すると良いようです。

また、embeddable に付属していない標準モジュールtkinterは、ここのようにして、使えるようにできました。

(編集済み)

これは経験則に基づくのですが、pythonのない環境のPCに配布するためのpythonアプリを作成する、一つのpythonバージョンに依存する(更新を行わない前提)等といった理由がなければembeddable zip fileを使用するのはおすすめしません。
(特に勉強目的でpythonをインストールするならおとなしくインストーラを使用したほうがいいです。)
embeddable zip fileは組込み等の業務目的またはpythonの仕組みを理解しており、セキュリティ等も含めた各種設定を全て自分で出来るといった上級者向けの配布パッケージになります。(情報も散在していおり、結構苦労しました・・・)

理由として、python公式ドキュメントではコマンドライン引数にファイルを指定して呼び出した場合、
(python C:\~~\main.py のようなコマンド)sys.path[0]にコマンドライン引数に指定したファイルのディレクトリパスをモジュール参照先設定として追加すると記述があり、
[https://docs.python.org/ja/3/library/sys.html?highlight=sys%20path]
インストーラ版ではその記載通りに動くのですが、embeddable zip fileではコマンドライン引数に指定したファイルのディレクトリをモジュール参照パスに指定してません。
(pip部分で起こっている問題がまさにそれです。)
[https://docs.python.org/ja/3/using/windows.html]
上記マニュアルの(3.8. モジュールの検索)にその挙動について説明があります。
embeddable zip fileでは[python〇〇._pth]があると隔離モードでの起動になるため全て自分でモジュール参照設定を明記する必要があります。
※隔離モードでは全てのレジストリと環境変数(PYTHONPATH)は無視されます。

これの対応方法としては、

①ソースコードに参照するモジュールのパスを一つ一つ設定する
②python.exeの含まれたディレクトリに[適当な名前.pth]を作成し、その中にpython.exeを起動した際に必ず参照させたいディレクトリパスを記述する
③python〇〇.__pthファイルを削除する(隔離モードの解除)

等がありますが、それぞれデメリットもあります。

①であれば自身のソースがどのモジュールを参照するか全て把握し、網羅して記載する必要があります(pipしたパッケージ、自作モジュール等)
②であれば小さなpythonプログラムが増える度、いちいちpthファイルに記載する必要がある上、同名のファイル、変数名が存在する場合多重定義エラーが発生します。
③を削除した場合はインストーラ版と同じ挙動をしますが、細かい設定ができなくなります。(embeddable zip file使う時点で細かい設定をすると言う前提になるのでそれなら最初からインストーラを使用するべきです。)

それ以外にもインストーラ版との違いとしては
・py.exeが含まれない
というのがあり、
py.exeはpythonランチャーと呼ばれており、複数のpythonバージョンを管理し、ソースコードに合わせて適切なバージョンを起動してくれるものになります。(新バージョンを試す目的で複数バージョンをインストールするとき等に効果を発揮します。)

インストーラ版ではインストール時に特殊な設定をしないとpythonインストール先がユーザー固有のディレクトリ(WindowsならAppData以下)にしかインストール出来ませんが、それはセキュリティ問題に対応する理由があります。
(全ユーザーがアクセス出来る様なディレクトリにpython.exeをインストールした場合、悪意あるスクリプトファイルやスクリプトを実行できる為)
それらも踏まえて全部自身で管理できる場合にだけ使用してください。
pythonではインストーラが複数存在しますが、含まれているものの違い、挙動の違いも把握しないといけません。(pythonに限りませんが・・・)

あなたもコメントしてみませんか :)
すでにアカウントを持っている方は
ユーザーは見つかりませんでした