自作PythonスクリプトをWindows実行バイナリに変換する場合、pyInstallerはとっても楽ちんなのであるが、問題もある。
実行ファイルがスタートされるまで、とても待たされることだ。

その遅さに耐えかねてpy2exeを試してみたところ、ビルドまでは面倒なものの、作成した実行ファイルはめっぽう速くて驚いた。
そういったわけで、py2exeでのWindows実行バイナリ作成方法について記す。

まず最初に、pyInstallerとpy2exeで果たしてどの程度差があるかを示す。

実行速度の違い

UNIX系だと、time; <実行ファイル> とすると所要時間を計測してくれるが、Windowsだとどのようにするのであろうか。
よく分からない。
あんまり興味もないのでWindowsにもあるtimeコマンドで大まかに測ってみた。

前提
・同じスクリプトを使用する
・バイナリは一つにまとめる
・バイナリの処理完了までを測る

結果
・pyInstallerだと5秒
・py2exeだと2秒

その差3秒。
たかだか3MBytesのプログラムでこの違いである。
ただ、py2exeを避けていたのには理由がある。

py2exeの制限

py2exeはバイナリ作成のために、別にsetup.pyを書かねばならない。
対するpyInstallerは、pyInstaller本体に自作スクリプトを引数として与えればいいだけ。こちらからすると面倒ではある。
また、pyInstallerを使っていた時には、特に意識しなかったMSVCR90.dllの考慮もしなければならない。
さらにさらに、エンコーディングにも注意が必要(後述)。

が、これだけの弱点を差し引いても起動時間の差は魅力。
くわえて、setup.pyは一度書いてしまえば使い回しもできるんである。
というわけで、py2exeに宗旨替えを決意した。

py2exe: インストール

http://www.py2exe.org/から。
ActivePythonであれば、pypm install py2exeでインストールできる。

py2exe: MSVCR90.dllの準備

py2exeでのバイナリ作成、およびバイナリの実行にはMSVCR90.dllが必要。

MSVCR90.dllはファイル名が同じでも複数バージョンが存在する。
悪いことにpy2exeは特定のバージョンを要求する。
そのバージョンが9.0.21022.8。
VisualStudioであれば「SP1でない」2008、あるいはこちらも「SP1でない」Microsoft Visual C++ 2008 Redistributable Package に含まれている。

x86向け
http://www.microsoft.com/ja-JP/download/details.aspx?id=29 x64向け
http://www.microsoft.com/ja-JP/download/details.aspx?id=15336

以下はあくまで参考
x86向けSP1
http://www.microsoft.com/ja-JP/download/details.aspx?id=5582 x64向けSP1
http://www.microsoft.com/ja-JP/download/details.aspx?id=2092

MSVCR90.dll、Microsoft Visual C++ 2008 Redistributable Packageのインストーラ(vcredist_x86.exe)ともに、プロパティからバージョンを確認することができる。

バイナリ作成者がDLL再配布の権利を持っている場合、DLLも含めて自作プログラムを配布できる。
権利がなければ、ユーザには上記のMicrosoft Visual C++ 2008 Redistributable Packageをインストールしてもらうこと。

py2exe:setup.pyって何よ。

py2exeは、バイナリ化じたいの処理を、pythonスクリプトで書く。
自作スクリプトのほかに、setup.pyを書く必要があるのはそのため。

py2exe: setup.pyの書きかた

http://www.py2exe.org/index.cgi/Tutorial

上記チュートリアルに沿って進める。

以下の通りのsetup.pyを作る。
ひとまず自作スクリプトと同じディレクトリが良いでしょう。
もちろん、jisaku.pyは自作スクリプト名に変更。

from distutils.core import setup
import py2exe

setup(console=['jisaku.py'])

そうしたら、setup.py, 自作スクリプトのあるディレクトリで、以下のように実行するだけ。

$ python setup.py py2exe
$

そうすると、同じディレクトリにdistというディレクトリが作られ、その下に実行ファイルができあがる。
簡単である。

あとはこのsetup.pyをベースに、いろいろと加えていけばよい。
py2exeのオプションは以下を参照のこと。
http://www.py2exe.org/index.cgi/ListOfOptions

python setup.py py2exeとタイプするのが面倒

sys.argv.append('py2exe')を加えてあげれば、それ以降python setup.pyだけでよくなる。

from distutils.core import setup
import py2exe, sys, os

sys.argv.append('py2exe')

setup(console=['jisaku.py'])

実行ファイルを一つにまとめるには

http://www.py2exe.org/index.cgi/SingleFileExecutable

前章で作成したsetup.pyをベースに、といいつつガラッと変わる。
とはいえ、基本的な書式は変わっていない。

from distutils.core import setup
import py2exe, sys, os

sys.argv.append('py2exe')

setup(
    options = {'py2exe': {'bundle_files':1}},
    zipfile = None,
    console = [{'script': "jisaku.py"}]
)

bundle_filesでファイルのまとめ方を指定する。
値は1でよい。
1は、とにかく一つにまとめる。
2だと、本体とPython.dllが分かれる。
zipfileもNoneでよい。
ここにファイル名を指定すると、各種DLLがそのファイル名でzipされる。
Noneであれば本体にマージされる。

本体を圧縮するには

速度でpy2exeを選択した私としては、あまり使おうという気にならないのだが。
手元の自作スクリプトから作成した5MBの実行バイナリが3.5MBになった。

from distutils.core import setup
import py2exe, sys, os

sys.argv.append('py2exe')

setup(
    options = {'py2exe': {'bundle_files':1, 'compressed': True}},
    zipfile = None,
    console = [{'script': "jisaku.py"}]
)

アイコンを追加するには

icon_resourcesで指定する。
http://www.py2exe.org/index.cgi/CustomIcons

from distutils.core import setup
import py2exe, sys

sys.argv.append('py2exe')

setup(
    options = {'py2exe': {'bundle_files':1}},
    zipfile = None,
    console = [{
            'script': "jisaku.py",
            'icon_resources': [(1, "jisaku.ico")]
            ,}]
)

実行時にコマンドプロンプトウインドウを開かないようにするには

consoleではなくwindowsにする。

from distutils.core import setup
import py2exe, sys

sys.argv.append('py2exe')

setup(
    options = {'py2exe': {'bundle_files':1}},
    zipfile = None,
    windows = [{
            'script': "tsuboneChecker.py",
            'icon_resources': [(1, "Artcore-Illustrations-Artcore-4-Terminal.ico")]
            ,}]
)

py2exeでのエンコーディング

py2exeでホクホクしていたら、スクリプトの実行は問題ないのに、ひとたびpy2exeでバイナリにすると、途端にエンコーディングエラーが出る、という症状に悩まされた。
結論としては、以下を加えれば解決。

if hasattr(sys, 'setdefaultencoding'):
    import locale
    lang, enc = locale.getdefaultlocale()
    sys.setdefaultencoding(enc or 'cp932')
    del sys.setdefaultencoding

http://www.py2exe.org/index.cgi/EvenMoreEncodings http://d.hatena.ne.jp/shive/20110626/1309061690