cx_FreezeでPythonからexeファイル生成

2023-02-16(木)

Pythonはスクリプト記述なので開発環境の無いところで利用しようとすると面倒ごとが多い。 Windowsなら.exe,macOSなら.appに変換するのがよく用いられる手段で,cx_Freezeはその一つ。

Pythonのスクリプトから実行形式を生成できるパッケージで,元のスクリプトにはほぼ手を入れる必要が無さそうなのが良い点。 ただしWindowsならWindows,macOSならmacOSでの作業が必要。クロスプラットフォームでの実行ファイル生成はできない。(開発は可能)

GUIアプリケーションのサンプルとしてはPyQt5, PyQt6, PySide2, PySide6, wxWidgets, tcltk, Python.Netあたり。OpenCVも普通にcv2.imshowが使えたりする。

(余談だがPython.NetのデモがIronPythonよりも遥かにC#のFormsアプリっぽく書けそうで驚いた。)

手順としては普通にプログラムを作ったうえで,ルールに従ってsetup.pyを記述したうえで

python setup.py build

または

python setup.py bdist_mac

とすれば.exeや.appを生成する。ただし実行に必要なdll等は内包せずに一緒にbuildフォルダにコピーされるので,フォルダごと持って行って実行。となる。OpenCLを使うパッケージでも普通に動いたのでかなり便利。

必要なファイルについては結構優秀で自動的に依存関係からコピーしてくれるのだが,逆に言えば依存関係の解析に失敗すれば全コケする。.pydのようなバイナリ化してるパッケージについては対応できてないようなので,pythonでスクリプトを走らせたときは動くのに,exe化したときには動かない,という現象が発生するうえに,printでデバッグできないので検証が面倒。 (importでSystemErrorを起こしてexeでだけ動かないプログラムも↓の方法で確認したらnumpyが無いと言われてたので,import numpyをそのパッケージの前にimportしたら動いた)

エラーの収集

.exeにするとprintデバッグができないがstdoutとstderrをファイルに振ってしまえば行けるだろうと思ったら既に先人が居たので有り難く使わせて貰う。

具体的には

import sys, os
class Log(object):
    def __init__(self, filename="log.txt"):
        self.d = sys.stdout
        self.l = open(filename, "a")
    def write(self, mm):
        self.d.write(mm)
        self.l.write(mm)
    def flush(self):
        self.d.flush()
        self.l.flush()

class LogFrozen(object):
    def __init__(self, filename="log.txt"):
        self.l = open(filename, "a")
    def write(self, mm):
        self.l.write(mm)
    def flush(self):
        self.l.flush()

def initstdout():
    localdir = os.path.expanduser("~/")
    if not os.path.exists(localdir):
        os.mkdir(localdir)
    if getattr(sys, "frozen", False): # frozen
        log = LogFrozen(os.path.join(localdir,"log.txt"))
        sys.stdout = log
        sys.stderr = log
    else: # not frozen
        log = Log(os.path.join(localdir,"log.txt"))
        sys.stdout = log
        sys.stderr = log
initstdout()
# この後に怪しいimportを書く

をスクリプトに入れておけばホームフォルダ(Windowsならc:\Users\username,macOSなら/Users/username)にlog.txtファイルで出力される。場所を変えたかったらlocaldir = os.path.expanduser("~/")のところの"~/"を適当に変える(チルダはホームフォルダの意味)

Category: Memo Tagged: Windows macOS Python cx_Freeze