Hatena::ブログ(Diary)

グニャラくんのグニャグニャPython備忘録 RSSフィード

2009-06-04

[][][]PyQt4で画像を扱うアプリケーションMac OS X上でバンドルする

MacPorts入れる

まっさらなMacに、XcodeMacPortsを導入する。

んで、

sudo port install python26 py26-pyqt4
sudo python_select python26

とすれば、開発環境が整う。かなりコンパイル時間がかかるので注意。

バンドルする

Mac OS Xで配布用バイナリ(.appのディレクトリ)にbundleするには、

py2appが定番のようだ。


んで、py2appでサックリ.appを作ってみたけど、画像の読み込みがうまくできない。

Qtのimageformatsプラグインの読み込みに失敗しているのであろう。


Mac OS Xの動的ライブラリには、dylibとbundleの2つの形式があるらしい。

Mach-Oの仕様で決まっているんだって。

dylibは共有ライブラリ、bundleは動的にロードできるモジュール、ということ。


dylibもdlopen()で開くことができるんだけど、dlclose()はできないっぽい。

bundleはdlopen()/dlclose()両方OK。

(本当は別のAPI群があるらしいけど、どうせラッパ関数しか使わないからねー)


Mac OS XQtのimageformatsは、bundle形式で提供されている。

というわけで、data_filesにbundle形式のライブラリを突っ込めばおk

なのかと思ったらうまくいかない。


py2appでバンドルした場合には、どうやらbundleの読み込みがうまくいかないようだ。

py2exeでbundle_filesを3以外に設定したときと同じようなエラーメッセージが表示される。


strace的なもので共有ライブラリのロードを調べたい。Macの場合にはktraceがそれにあたる。

もしくは、おそらくpy2appはpy2exeのbundle_filesが3以外の挙動と似ている可能性があるので、

py2exeのほうで問題を追ったほうが理解しやすいかもしれない。

なにせ、Macでの開発は経験が足りないからねー…


PyInstallerの場合にはもしかしたらimageformatsの読み込みに対応しているのかもしれない、

という希望があったりします。

対応していなくても、開発が動いているのであればパッチも送れるし。

そうすると、Windows版の.exe作成もPyInstallerでいいじゃん、という話になり、

結局cx_Freeze->py2exe->PyInstallerと全部試してしまうことになる悲しい結末です。

でも、PyInstallerってWindows上で使いづらいんだよねぇ…

2009-05-31

[][]cx_Freezeからpy2exeに移行作業したときのメモ

py2exeはencodings以下を手厚く入れてくれる。

cx_Freezeの場合、encodings以下のモジュールについて必要なものは、おのおの指定する必要があった。

py2exeはencodings以下をどっちゃり入れてくれる。

py2exeでPyQtで画像を扱う場合の注意

前エントリで紹介したように、PyQtで画像ファイルを扱っている場合には、

imageformats以下にあるQtプラグインを配置しなければならない。

cx_FreezeでPyQtアプリを固めた時に詰まったところ - グニャラくんのグニャグニャPython備忘録


が、僕はココでかなり詰まった。

imageformats以下のファイルを配置しているのにロードしてくれないのだ!

しかも、コンソールには以下のようなエラーが大量発生する。んぺぺ!

QObject::moveToThread: Current thread(xxxxxxxx) is not the object's thread(xxxxxxxx).

Cannot move to target thread (xxxxxxxx)

長い間試行錯誤した挙句、原因が判明。

'bundle_files' = 3にすること!

あああああああ

py2exe実行時に、msvcp90.dllがねーよ!といわれる

C:\Python26に、msvcp90.dll/msvcm90.dll/msvcr90.dllの3つのファイルを放りこんだらpy2exeが実行できた。

py2exeで作ったバイナリが、他の環境で「初期化に失敗」だの「構成が正しくない」だのエラーを出す

Visual C++のCランタイムライブラリ(さっきのmsvcp90.dllみたいなの)のロードに失敗しているため。

手っ取り早いのは、以下のファイルをインストールすること。


しかし、僕も正直

Pythonインストールするのが面倒なのでexeに変換したのに、わざわざ別のものインストールさせたら手間かわんねーじゃん?

http://d.hatena.ne.jp/AMENOHI/20090202/1233575464

ってな気分です。


cx_Freezeの場合には、msvcp90.dllを配布物の中に入れてくれます。

py2exeの場合には、Windowsの標準システムDLLかどうかを判定するロジックによって

依存DLLを配布するかしないかを判断する関数があり、

その関数による判定の結果、msvcp90.dllは配布されません。


というわけで、判定関数をのっとります。

origIsSystemDLL = py2exe.build_exe.isSystemDLL
def isSystemDLL(pathname):
  if os.path.basename(pathname).lower() in ('msvcp90.dll'):
    return 0
  return origIsSystemDLL(pathname)
py2exe.build_exe.isSystemDLL = isSystemDLL

これでmsvcp90.dllはdistディレクトリの中に放り込まれたのでした。わーい!


んでも、カレントディレクトリにmsvcp90.dllがあったからといって、

それが使われるわけじゃないらしいんですね。

Microsoft.VC90.CRT.manifest」ってファイルをexeのカレントディレクトリに放りこめばいいのかもしれないし、

setup()のconsole引数に、

{'other_resources': [(24, 1, manifest)]}

的なものを指定して、そのmanifest中でVCランタイムを指定すればいいのかもしれない。

まだ調査できてないし理解も出来ていない。調査できたら追記すると思う。

2009-05-30

[][]cx_FreezeでPyQtアプリを固めた時に詰まったところ

cx_Freezeにて、setup.pyで設定を行い場合の注意

たとえば、以下のような要件があったとする。

sipモジュールを追加で読み込みたい。

アプリケーションアイコンを指定したい。

cx_Freezeのドキュメントでは、

以下のような書き方でOKなように読める。

import sys
from cx_Freeze import setup, Executable

base = None
if sys.platform == 'win32':
  base = 'Win32GUI'

exe = Executable(script = 'app.py',
                 includes = ['sip'],
                 icon = 'app.ico',
                 base = base)
setup(name = 'myapp',
      version = '0.0.1',
      description = 'my test application',
      executables = [exe],
)

でも、これだとNG。


以下の記法でうまくいった。

import sys
from cx_Freeze import setup, Executable

base = None
if sys.platform == 'win32':
  base = 'Win32GUI'

exe = Executable(script = 'app.py',
                 icon = 'app.ico',
                 base = base)

setup(name = 'myapp',
      version = '0.0.1',
      description = 'my test application',
      executables = [exe],
      options = {
        'build_exe': {
          'includes': ['sip'],
        },
      })

cx_Freezeで固めたバイナリでのみ、画像が表示できない

詳しくは、

を参照のこと。

たとえば、imageformats/qjpeg4.dllの配布も行いたい場合、

cx_Freezeのoptionsはこうなる。

      options = {
        'build_exe': {
          'includes': ['sip'],
          'include_files': [('imageformats/qjpeg4.dll',
                             'imageformats/qjpeg4.dll')],
        },
      }

僕はめんどうくさがりなので、

標準ライブラリパスであるアプリケーションディレクトリにimageformatsをコピーすることによって回避した。

cx_FreezeとPyQt4で生じる問題

cx_Freezeは、Windowsインストーラパッケージとして

msiファイルを生成する。

msiファイルを生成する際、Pythonのmsilibモジュールを使っているのだが、

複数個のドットが含まれるファイル名のパッケージングで不具合がある。


よって、PyQt4.QtCore.pydとPyQt4.QtGui.pydのパッケージングに問題が生じた。

上記issue1128に投稿されたパッチを適用して解決した。

cx_Freezeでbase = 'Win32GUI'にした場合に、sys.exit()でエラーダイアログが出る問題

項目名のとおりで、sys.exit(0)なんかを実行してしまうとエラーダイアログが出る。

cx_Freezeのsource/bases/Win32GUI.cを読んでみると、

SystemExit例外をハンドルしている部分でエラーダイアログが出ているようだ。

    PyErr_Fetch(&type, &value, &traceback);
..(snip)..
    if (PyInt_Check(value))
        exitCode = PyInt_AsLong(value);
    else {
        message = StringifyObject(value, &valueStr);
        MessageBox(NULL, message, "cx_Freeze: Application Terminated",
                MB_ICONERROR);
        Py_XDECREF(valueStr);
        exitCode = 1;
    }

んだもんで、試しに、

raise SystemExit, 0

としてもやっぱりエラーダイアログが出る。もう知らん!(不貞寝

まとめ

cx_Freezeは現在メンテされていない、とどこかのMLアーカイブで読んだし、

pyInstallerはsvn headを使えと言ってるし、

やはり素直にWindowsバイナリ生成はpy2exeを使うか…

2009-05-08

[]PyQtアプリを作る

PyQthtmlをごにょごにょするアプリを作りたい@Windows

今まではwxPythonを使っていたが、画面デザインツールがいまいち使いづらい。

Qtのデザインツールを使ってみたらよさげだったので、PyQtに移行しよう。

最近LGPL 2.1もライセンスの選択肢に入ったので、もはや避ける理由もないだろう。

(追記)PyQtそのものは、現在GPL/商用ライセンスのみ選べるようです。情報提供元は、http://d.hatena.ne.jp/methane/20090509/1241873274

SQLiteまで入っていてオトクですね。


htmlの解析は、BeautifulSoupを使うのも手だが、

今回の用途ではパース速度も重視したい。よってlxmlを使う。


以下のものをダウンロードする。

setuptoolsは、Windows向けPython 2.6用パッケージが存在しない。

よって、setuptools-0.6c9.tar.gzをダウンロードする。

解凍後、

c:\Python26\python setup.py bdist_wininst

的な感じでdist\setuptools-0.6c9.win32.exeが出来る。これをインストールする。


setuptoolsが入れば、lxmlはeasy_install.pyを使って導入できる。

c:\Python26\Scripts\easy_install.exe lxml

setuptoolsなしで、lxmlのパッケージlxml-2.2.win32-py2.6.exeを入れてもよい。

setuptoolsがあれば、他にも便利なパッケージを導入できるだろう。


PyQtで作ったアプリを配布する

WindowsのexeとしてPyQtアプリを配布したい。

定番としてpy2exeがあるが、今回はcx_Freezeを用いる。

コイツはLinuxでも使える。

cx_FreezeでPyQtやlxmlを用いたスクリプトを配布する場合には気をつける必要がある。

なぜなら、cx_Freezeでは検知できないモジュールが実行のために必要となるからだ。

PyQtの場合はsip、lxmlの場合はlxml._elementpathなどを教えてあげる必要がある。

僕が作ったアプリの場合、gzipやencodings.utf_8・encodings.asciiも必要だった。


具体的には、以下のようなスクリプトで実行ファイル一式を得ることが出来た。

c:\Python26\Scripts\cxfreeze --include-modules="sip,lxml._elementpath,gzip,encodings.utf_8,encodings.ascii" application.py

Qt/PyQtのドキュメントを読む

PyQtは、ローカルのドキュメントツールがある。

QtPyQtのドキュメントは以下のサイトでも読むことができる。

2008-11-23

[]PyTC 0.5をリリースしました。

PyTC、ビルドできなくなっていたんですね…外国人の方に「ビルドできひん」というメールもらって初めて気づきました。

tchdb.hからいくつかの定数がなくなっていたので、それらを削除して新しい定数を加えたPyTC 0.4をリリースしました。さらに、addint/adddoubleを追加し、キーがない場合にはPyTCErrorの代わりにKeyErrorを出すようにしたPyTC 0.5をリリースしました。

addint/adddoubleについては、Python整数/小数を渡して、足した結果の整数/小数が返ってきます。が、値の初期化や通常の取り出しはバイナリで行われるので、struct.pack/unpackを使ってください。具体的には、以下のようにしてください。これで、auto_incrementサーバ的なものが簡単にできますね。

    # 0で初期化
    db['int'] = struct.pack('i', 0)
    # 1を足して、足した結果を表示
    print db.addint('int', 1)
    # 結果を表示
    print struct.unpack('i', db['int'])[0]
    # 結果表示について、めんどい人はこうしてもOK
    print db.addint('int', 0)

[追記]BZIPオプションのサポート忘れてました

というわけで、BZIPオプションを付与したPyTC 0.6をリリース!