cx_Freeze ビルド時によく遭遇するエラーとその対処方法

NameError: name 'file' is not defined

__file__ は使えない。

(対策) sys.argv[0] などで補う。

NameError: name 'exit' is not defined

exit() は使えない。

(対策) sys.exit() を使う。

AttributeError: 'NoneType' object has no attribute 'write'

argparse を例にする。

argparse は Usage や「コマンドライン引数が不正ですよ」といった旨を標準出力するが、cx_Freeze で Win32GUI でビルドしている場合は標準出力担当が消えてなくなる(標準出力を行う機能を持ったオブジェクトが None になる)ため、このようなエラーが出る。

対策1: try-except でラップする

たとえば拙作のプログラムランチャ incl では 例外時のエラーメッセージをシンプルにした上で、winapi の MessageBox で表示させる という対応をしている。素のエラーメッセージ(スタックトレース)全文を表示するよりは全然優しい。

対策2: 標準出力先を nul(null) に置き換える

トリッキーな方法として「標準出力自体を無効化する」やり方もある。

import os
import sys

# 標準出力先を nul(null) に置き換える
null_fp = open(os.devnull, 'w')
sys.stdout = null_fp

詳しくは Redirecting stdout to "nothing" in python - Stack Overflow に書いてあるが、sys.stdout に nul や null に相当するディスクリプタを指定させることで何も出力させないようにするというもの。

ただしこれを行うと 開発時(たとえばコンソールから実行する時)も標準出力が一切見えなくなってしまう ため不便だ。開発時は普段通り表示し、リリース時にのみ nul 指定させる、など一工夫必要だろう。

ImportError: can't find module 'XXXXXXXXmain'

実行ファイル名を変更すると起きる。

cx_Freeze ではエントリーポイントとなるスクリ hoge.py に対し、実行ファイルは hoge.exe となっている。内部的には

  • hoge という名前のメインモジュールが定義されている
  • 実行ファイル実行時に実行されるメインモジュール名は 実行ファイル名から(動的に)生成する

となっているので、実行ファイル名を変えてしまうとメインモジュールに到達できなくなり、このエラーが出る。

対策: 実行ファイル名は変更しない

変更したい場合は、エントリーポイントとなる py ファイル名を変更する。

上記例で言えば、hoge.py をビルドして出来た hoge.exe を fuga.exe に変更するのではなく、hoge.py を fuga.py に変更した上でビルドする(すると fuga.exe ができる)。

ImportError: DLL load failed: 指定されたモジュールが見つかりません。

実行ファイルのカレントディレクトリ(実行時ディレクトリ)が「実行ファイルのあるディレクトリ以外」になっている時に起きる。

実行ファイルが依存する DLL(たとえば Tkinter の DLL)には相対パスでアクセスするわけだが、カレントディレクトリが変化しているとここが合わなくなる。

(対策) 不明。良い方法があったら誰か教えてください……。