読者です 読者をやめる 読者になる 読者になる

空飛ぶ気まぐれ雑記帳

主に趣味とかプログラミングについて扱います。

PythonでGUIアプリケーションを作ろうとした話

はじめに

今回の話はタイトルどおり、Pythonを使ってGUIアプリケーションを作ろうとした話です。
これまでに、C++でアプリケーションを作ったことは数多くあれど、他の言語でアプリケーションを作った経験は少ないものです。
特に、スクリプト言語に絞ると無いと言っても過言ではないでしょう。

というのも、スクリプト言語というと、研究で何かしらの実験をして、その実験結果を整理するためのもの。
というイメージが強く、どうしても書捨てなイメージが強かったからです。

その一方で、C++でアプリケーションを作ると常に膨大な行数のソースコードとファイル数で、1つ作り上げるのに結構な時間を要するという至極一般的な問題がありました。
その上、実装中に「この書き方はできるだろ」と思ってたら実は駄目ですということがママあり*1、急遽設計を変更する羽目に会うことがあります。

そのため、あまりC++でアプリケーションを書ききったことがないという現実があります*2

いくらなんでもそれは…と思い至り、少しでも完成率を上げるために、それならスクリプト言語で書けば良いんじゃねと結論に至りました。

そんなわけで、今回紹介するのはPythonGUIアプリケーションを作る過程の話を紹介したいと思います。

GUIライブラリの選定

tkinter

言わずと知れた、Python付属のGUIライブラリ。
比較的簡単に使えるらしいのですが、どうも完成しても見た目があまり良くないそうで、ユーザー数が少ないそうです。
実際Googleで検索してもあまり込み入った話を書いたブログが見つからなかったので、実際少ないのでしょう。
というわけで、今回はボツ案で。

wxPython

wxWidgetsPythonバインディング
C++版に比べて使いやすいらしいのですが、wxWidgetsWindows版はとにかくバグが多い*3上に、かなりお作法に厳しいので、あまり好きではありません。
というわけでボツですね。

PyQt5

Qt5のPythonバインディング
文字列を標準のstring型ではなく、QString型をバインディングしたクラスを使ったりと結構面白いバインディングの仕方をしているライブラリです。
イベントの扱いにC++版と若干違いがあって、connectでSIGNALとSLOTというマクロを使って接続していたのですが、signal型のconnectを使ってslotを直接接続する感じになっています。
書いていてよくわからないので、例をあげます。実際にボタンのクリックイベントを使う時は下記のようにします。

button.clicked.connect(self.onClicked)

ただ、このライブラリ。
色々あって、ライセンスがLGPLではなく、GPLでしかライセンスされていません。
そのため、開発したアプリケーションにもGPLでライセンスをする必要があるため、注意が必要です。

そのため、今回はできればGPLでライセンスしたく無かったのでボツにしました。

PySide

PyQt5の作者が作ったLGPLでライセンス可能なQt5のPythonバインディング
PyQt5との違いは文字列型にPython標準のstringを使っているなどなど。
詳しくはググってください。

基本的にはC++版とのQtと使い方に違いは無いので、慣れていることもあって、今回はPySideにしました。

アプリケーションのデプロイ

Pythonで作ったアプリケーションのデプロイについてです。
通常Pythonスクリプト言語ですので、実行にインタプリタを必要とします。
しかし、それだと、自分が作ったアプリケーションをプログラマでないユーザに使ってもらおうとすると、ユーザにもPythonインタプリタをインストールすることを強いる必要があります。
それを回避するためのデプロイ用のアプリケーションを選定します。

今回作成したアプリケーションはPython3系で書いたので、Python3系で問題なく動作するアプリケーションが必要です。

下記で紹介しているアプリケーション全てpipからインストール可能なのでインストールも楽に済みます。

py2exe

多分一番有名なデプロイアプリケーション。
py2appなど、Windwos環境以外に向けた実装も用意されているため一応マルチプラットフォームで活用することが出来ます。
しかし、私の環境(Python3.5.2)ではどうもうまく動作せず。
実行中にエラーメッセージを吐いて異常終了してしまったので、うまくデプロイすることが出来ませんでした。

PyInstaller

py2exeでは、アプリケーションのデプロイに設定ファイルとしてのPythonスクリプトが必要なのですが、こいつはそれを必要としません。
ソースコードを解析して、必要なDLLを決定しているそうなのですが、その解析があまり賢くないため、過剰にDLLを集めてしまうそうです。
ただ、こいつの場合、entryポイントエラー(だったかな)DLL不足ではない謎の例外を吐いて、私の環境では上手く動いてくれなかったので、諦めました。

cx_Freeze

前2つと何が違うかと言われると「なんだろう?」ってなるくらいに、よく調べていません。
それはさておき、こいつは、py2exeと同様、設定ファイルを必要とするタイプですが、結構安定して動作するそうです。
それで実際に試してみると、一部DLLが不足してEXE起動時にDLL無いよって例外吐いて落ちてしまいました。
なんだか、進歩があったようなので、もう少し調べてみると、そういう場合はpackagesに含まれなかったライブラリを手動で足せば良いそうです。

実際に書いたsetup用のスクリプトは下記の通りです。
いまいちpackagesとincludesの違いはわかりませんが…とりあえず、こう書くそうです。

import sys
from cx_Freeze import setup, Executable

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

packages = ['lxml']      # これがデプロイされなかったライブラリ
includes = ['PySide']    # デプロイ時に含めて欲しいライブラリ 
excludes = ['PyQt4']     # デプロイ時に含めて欲しくないライブラリ

setup(  
	name = 'Scripter',  # アプリケーション名
	version = '0.1',    # アプリケーションのバージョン
	description = '',
	options = { 'build_exe' : {'includes':includes, 'excludes':excludes, 'packages': packages} },
	executables = [Executable('main.py', base=base, icon='ICON.ico', targetName='Scripter.exe')] 
	#出力するアプリケーション名やアイコンファイルを設定
)

インストーラの作成

インストーラの作成というとアプリケーションを本格的にユーザにデプロイする場合にしか必要としないため、あまり需要がない or そういうことをする人は自分で調べられるからなのかはわかりませんが、このあたりは日本語資料があまりないので、とりあえず書いておこうと思いました。

Install Shield

いわずと知れた、インストーラの代名詞的存在。いろんなソフトで見かけるかと思います。
しかし、こいつ、ライセンス条項が結構怪しいらしく、使用するPCをInstallShieldの開発元に監視されるらしく結構やばい。
というわけで、今回は候補から外します。

WiX

Microsoftが現在オープンソースで開発しているアプリケーションで、XMLベースのマークアップを記述することでインストーラを作成することができます。
ただ、その仕様とXMLの言語仕様が相まって書くのが非常にめんどくさい。
その上、資料と言える資料が英語Wikiのみで学習コストも高いですし、そもそもファイル数が増えると書くのがめんどくさい。

NSIS

NSIS Wiki
というわけで、今回の本命。
こいつはWiXと同じくスクリプト形式のインストーラ作成アプリケーションなのですが、文法がスペース区切りのbashの設定ファイルのような形式になっていて、WiXとくらべて書きやすい文法になっています。
また、こいつの優れている点としてzipファイルからインストーラを自動生成できる点があります。
その設定も非常に簡単で、zipファイルを選択して、あとはOKボタンを押すだけでできます。

おわりに

今回はPythonアプリケーションをデプロイしてインストーラを作成するところまで紹介しました。特にインストーラを作る部分は初めてやってみましたが非常に簡単でした。
が、作ったアプリケーションをいかに公開するかという難題がまだ残っています。
それについては少々悩んでいるので、また今度。

*1:え、文法を覚えて無いのかって?いいえ、そういう訳ではなく。Qtお前のことだよ。

*2:だいたい10個に3個ぐらいの割合でしか完成までたどり着きません

*3:OpenGLのコンテキストが解法されずメモリリークするとか、Sliderオブジェクトの再描画がされないとか