みーのぺーじ

みーが趣味でやっているPCやソフトウェアについて.Python, Javascript, Processing, Unityなど.

cx_Freezeでもprint()の出力を見る方法

cx_FreezeでPythonスクリプトをフリーズした時に不便なこととして,print()の出力が見れなくなることがあります.これを解消するための方法を考えました.

sys.stdoutとsys.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("~/.hoge")
    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()

class Logは,フリーズされていない時のファイル様オブジェクトです.def write()を実装しているので,sys.stdoutに指定することが可能です.このクラスでは,write()で書き込み内容を通常のsys.stdoutとファイルに渡します.

class LogFrozenは,フリーズされた時のファイル様オブジェクトです.フリーズされていると,sys.stdoutが使えないので,ファイルのみに出力するようにしています.

両者でflush()を指定しているのは,アプリケーションの終了時など,すべての出力を完了する必要があるときに適切な処理が行われるようにするためです.

os.path.expanduser()は,\~をホームディレクトリと置き換えてパスを返します.わざわざディレクトリを指定するのは,cx_Freezeするとカレントディレクトリが書き込み不可のディレクトリとなるようで,エラーが出るからです.つまり,書き込みが明らかに可能なディレクトリならばどこでも大丈夫です.mkdir()で,上記ディレクトリが存在しなければ作成します.

getattr(sys, "frozen", False) は,フリーズされていればTrueを,そうでなければFalseを返します.ここでフリーズされているかの識別を行います.それぞれの場合に応じて適切なLogクラスのインスタンスを作成し,sys.stdoutとsys.stderrに指定します.

上記のスクリプトを,プログラムの最初に記述すれば,全てのprint()での出力が指定したファイルに出力されます.cx_Freezeしたアプリケーションでもprint()の出力が読めるようになるので,デバックに有用かと思います.

なお,上記スクリプトはMac OS XのMavericks 10.9.4 のpython 3.3.5と py33-cx_Freeze  4.3.1 で検証しています.