PySide6でデスクトップアプリを作る #7 パッケージングと配布 — PyInstallerで実行ファイルを作る

アプリが完成しても、python main.pyで起動している間は開発者の持ち物です。ほかの人に渡すには最後の関門が残っています。受け取る側のコンピュータには、PythonもPySide6もインストールされていないという事実です。今回はインタープリタとライブラリまでまるごとまとめて、ダブルクリックで起動する配布物を作る過程を扱います。シリーズの最終回です。

全7回で構成します。

  • #1 PySide6とは — QtとPythonでデスクトップアプリ
  • #2 ウィジェットとレイアウト — 画面を組み立てる
  • #3 シグナルとスロット — イベント処理の核心
  • #4 Qt DesignerとUIファイル — 画面を描いて読み込む
  • #5 モデルとビュー — リスト・テーブルにデータをつなぐ
  • #6 スレッドとタイマー — 止まらないUI
  • #7 パッケージングと配布 — PyInstallerで実行ファイルを作る ← この記事

何をまとめる必要があるのか #

配布物には3つの層が一緒に入っていなければなりません。自分の書いたコード、PySide6とQtのライブラリ、そしてPythonインタープリタそのものです。この3層を1つの実行ファイル(またはフォルダ)にまとめてくれるツールがPyInstallerです。最も広く使われていて資料も多いため、最初の配布ツールとして適しています。

PyInstallerのインストール
uv add --dev pyinstaller

配布ツールはアプリの実行に必要な依存関係ではないので、--devで開発用依存関係に入れます。

最初のビルド — 基本はonedir #

プロジェクトのルートで1行から始めます。

基本のビルド
pyinstaller --windowed --name TodoApp main.py
  • --windowed — 起動時に黒いコンソールウィンドウを開きません。GUIアプリでは必須のオプションです。
  • --name — 成果物の名前を指定します。

ビルドが終わるとdist/TodoApp/フォルダができ、その中に実行ファイルとライブラリのファイルが一緒に入っています。このフォルダ全体が配布の単位で、フォルダごと圧縮して渡せば、受け取る側は展開して実行ファイルをダブルクリックするだけです。これがデフォルトのonedir方式です。

ファイル1つにしたい場合は--onefileを付けます。2方式の違いは、配布のしやすさと起動速度の交換です。

項目onedir(デフォルト)onefile
成果物フォルダ1つ(ファイル多数)実行ファイル1つ
起動速度速い遅い(起動のたびに一時フォルダへ展開)
受け渡しやすさ圧縮して渡すファイル1つで簡単
問題の診断容易(構成が目に見える)困難

受け渡しが簡単だという理由でonefileを選ぶ例は多いのですが、起動のたびに全体を一時フォルダへ展開するため、Qtアプリのようにライブラリが大きい場合は起動が目に見えて遅くなります。インストーラーや圧縮ファイルで渡せるなら、onedirが無難なデフォルトです。

データファイルの同梱 — .specと–add-data #

PyInstallerはPythonコードのimportをたどって、まとめる対象を収集します。そのためコードではないファイル、たとえば#4で作った.uiファイルやアイコン画像は自動では入りません。--add-dataで直接指定します。

データファイルの同梱(macOS・Linuxはコロン、Windowsはセミコロン)
# macOS / Linux
pyinstaller --windowed --name TodoApp --add-data "ui/main.ui:ui" main.py

# Windows
pyinstaller --windowed --name TodoApp --add-data "ui/main.ui;ui" main.py

区切り文字の左が元のパス、右が配布物の中でのフォルダです。オプションが増え始めたら、毎回コマンドラインに付けるのではなく、最初のビルドで生成されたTodoApp.specファイルを編集する方が管理しやすくなります。specはビルド設定を持つPythonファイルで、以後はpyinstaller TodoApp.specで同じ設定を再現できます。

ヒント
コードでデータファイルを開くとき、相対パスをそのまま使うと配布物でファイルが見つからないことがあります。PyInstallerの実行環境では展開先の場所がsys._MEIPASSに入るので、「開発中はソースフォルダ、配布物ではsys._MEIPASS」を基準にパスを計算するヘルパー関数を1つ置くのが定石です。

プラットフォーム別の注意点 #

PyInstallerはクロスコンパイルをサポートしません。Windows向けはWindowsで、macOS向けはmacOSでそれぞれビルドする必要があります。CIを使うなら、OS別のランナーで並列にビルドする構成が一般的です。

  • Windows--windowedを忘れると、アプリの後ろにコンソールウィンドウが一緒に開きます。アイコンは--icon app.icoで指定し、.ico形式である必要があります。
  • macOS--windowedを付けるとdist/.appバンドルが生成されます。ほかの人のコンピュータで警告なしに起動するには、Apple Developer証明書での署名(codesign)と公証(notarization)が必要です。署名のないアプリはGatekeeperが起動をブロックし、回避手順を要求します。個人配布の段階では、この事実を案内文で伝えるところから始めても構いません。
  • Linux — ビルドしたディストリビューションと同系統・近いバージョンの環境でよく動きます。広い互換性が必要なら、古いディストリビューションでビルドする慣行があります。

配布サイズの感覚は先につかんでおく方がよいです。Qtライブラリが一緒に入るため、シンプルなアプリでも配布物は数十MB規模になります。これはPySide6配布の正常範囲で、使っていないQtモジュールを除外するチューニングで一部を減らせます。

ウイルス対策ソフトの誤検知 #

WindowsではPyInstallerの成果物がウイルス対策ソフトに誤検知されることがときどきあります。実行ファイルが自分自身を展開して実行する構造が、一部のマルウェアと似ているためです。根本的な対応はコード署名証明書で実行ファイルに署名することで、署名が難しい段階なら、onefileの代わりにonedirを使うだけで誤検知が減る場合が多いです。配布前に自分の成果物をウイルス対策ソフトで一度スキャンし、案内文を用意しておくと問い合わせ対応が楽になります。

配布前の点検チェックリスト #

ビルドが成功したことと、他人のコンピュータで動くことは別の問題です。開発マシンにはPythonや各種ライブラリがすでに入っているため、配布物にファイルが欠けていても症状が現れないことがあるからです。渡す前に最低限、次を確認します。

  • クリーンな環境での実行テスト — Pythonが入っていない仮想マシンや別のコンピュータで配布物を起動してみます。この1回のテストが配布事故の大半を防ぎます。
  • データファイルの確認 — .uiファイル、アイコン、設定テンプレートが配布物に実際に入ったか、dist/フォルダを目で確認します。
  • コンソールビルドでエラー確認 — 配布物が理由なく終了するときは、--windowedを一時的に外してビルドすると、コンソールにエラーが出力されて原因を探しやすくなります。
  • バージョン表記 — ウィンドウタイトルや情報ダイアログにバージョンを表記しておくと、ユーザーから問い合わせが来たときにどのビルドかをすぐ特定できます。

公式の代替 — pyside6-deploy #

PySide6には公式の配布ツールであるpyside6-deployも含まれています。内部的にはPythonコードをCに変換してコンパイルするNuitkaを使い、設定ファイル(pysidedeploy.spec)ベースで動作します。起動速度と難読化の面で利点がありますが、ビルド時間が長く、問題診断の資料はPyInstallerほど多くありません。PyInstallerで配布の流れを先に身につけてから、必要になった時点で検討する代替として覚えておけば十分です。

シリーズ全体のまとめ #

7回の核心を1行ずつ整理します。

  • #1 PySide6はQtの公式Pythonバインディングで、LGPLライセンスなので商用アプリまで作れます。
  • #2 画面はウィジェットをレイアウトに入れて組み立て、座標の代わりにレイアウトが配置を計算します。
  • #3 ウィジェットの出来事はシグナルとして発信され、シグナルをスロットにつなぐことがイベント処理のすべてです。
  • #4 Qt Designerで画面を描いて.uiファイルを読み込めば、画面構成とロジックが分離されます。
  • #5 データが増えたらウィジェットに直接入れず、モデルとビューに分離します。
  • #6 重い処理はWorkerスレッドへ移し、シグナルで結果を受け取って、UIを止めないようにします。
  • #7 PyInstallerでインタープリタまでまとめれば、Pythonが入っていないコンピュータでも動きます。

おわりに #

ここまで進んできたなら、ウィンドウを開き、画面を組み立て、イベントを処理し、データをつなぎ、止まらないUIを作り、配布物まで作るという1サイクルを完走したことになります。次のステップとしては、宣言型UIのQMLとQt Quick、モデル/ビューの深掘り(ソート・フィルタのプロキシモデル)、Qt Linguistによる国際化といったテーマが続きます。必要になった時点で公式ドキュメントの該当章を読み進められる基礎は、すでに整っています。

以上でPySide6でデスクトップアプリを作るシリーズを終わります。

X