PySide6でデスクトップアプリを作る #1 PySide6とは — QtとPythonでデスクトップアプリ
モダンPython基礎まで終えると、Pythonでたいていの自動化スクリプトやCLIツールは作れるようになります。ところが、そのツールを同僚や家族に渡した瞬間、壁に当たります。ターミナルを開いたことのない人に「仮想環境を有効化してスクリプトを実行してください」と案内するわけにはいきません。ボタンと入力欄のあるウィンドウが1つ必要になる時点であり、このシリーズはそのウィンドウの作り方を扱います。Pythonトラックのデスクトップ拡張です。
全7回で構成します。
- #1 PySide6とは — QtとPythonでデスクトップアプリ ← この記事
- #2 ウィジェットとレイアウト — 画面を組み立てる
- #3 シグナルとスロット — イベント処理の核心
- #4 Qt DesignerとUIファイル — 画面を描いて読み込む
- #5 モデルとビュー — リスト・テーブルにデータをつなぐ
- #6 スレッドとタイマー — 止まらないUI
- #7 パッケージングと配布 — PyInstallerで実行ファイルを作る
今回はPython GUIの選択肢の中からPySide6を選ぶ根拠を立て、インストール後に最初のウィンドウを表示してイベントループが何かを確認するところまで進めます。
Python GUIの選択肢 #
Pythonでデスクトップ画面を作る方法は、大きく3系統に分かれます。
| 系統 | 代表 | 特徴 |
|---|---|---|
| 標準ライブラリ | tkinter | インストール不要。ただしウィジェットの種類が少なく、デフォルトの見た目が古いです |
| Qtバインディング | PySide6、PyQt6 | 成熟したウィジェットセット、ネイティブに近い外観、商用ソフトウェアでの実績多数 |
| Web技術ベース | Electron系、pywebview | Webフロントエンドを再利用できます。代わりにブラウザエンジンを同梱するため重いです |
シンプルな社内ツールならtkinterでも十分です。しかしウィジェットが増えて画面が大きくなるほどtkinterは手がかかり、WebベースにはWebベースで「メモ帳レベルのアプリが数百MB」という重さの問題がついて回ります。その間に位置するのが、ネイティブ水準の性能と豊富なウィジェットをPythonの文法のまま使える選択肢であるQtバインディングで、このシリーズはその中のPySide6を使います。
PySide6とPyQt6 — 同じQt、異なるライセンス #
QtをPythonから使うバインディングは2つ共存しています。名前が似ていて紛らわしいものの、APIはほぼ同じで、決定的な違いは誰が作り、どのライセンスで配布しているかです。
| 項目 | PySide6 | PyQt6 |
|---|---|---|
| 開発主体 | Qt Group(Qt公式) | Riverbank Computing(サードパーティ) |
| ライセンス | LGPL v3(商用アプリを無料で配布可能) | GPL v3 または有料の商用ライセンス |
| 公式名称 | Qt for Python | PyQt |
| APIの違い | Signal、Slot表記などの細かな差 | pyqtSignal、pyqtSlot表記 |
核心はライセンスです。LGPLのPySide6ならソースコードを公開しない商用アプリを無料で作って販売できますが、GPLのPyQt6で同じことをするには商用ライセンスの購入が必要です。学習資料を検索するとPyQtの記事のほうが多く出てきますが、APIがほぼ同じなので相互に参照でき、新しく始めるなら公式サポートがあってライセンスの負担もないPySide6が無難な選択です。
インストール #
プロジェクトディレクトリを作り、PySide6をインストールします。モダンPythonトラックで使ったuvをそのまま使います。
uv init todo-app
cd todo-app
uv add pyside6pip install pyside6インストールが終わると、数百のQtモジュールが一緒に入ります。このシリーズで主に使うのは、ウィジェットが集まったQtWidgetsと、シグナルやタイマーなどコア機能のQtCoreの2モジュールです。
最初のウィンドウを表示する #
さっそくウィンドウを1つ表示してみます。20行で足ります。
import sys
from PySide6.QtWidgets import QApplication, QMainWindow, QPushButton
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("はじめてのPySide6アプリ")
self.resize(400, 300)
button = QPushButton("クリックしてみてください")
self.setCentralWidget(button)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())uv run python main.pyボタン1つがいっぱいに広がったウィンドウが表示されれば成功です。短いコードですが、この構造がシリーズ全体の骨組みなので、1行ずつ確認しておきます。
QApplication— アプリ全体を管理するオブジェクトです。どのウィジェットよりも先に、プログラムごとに正確に1つだけ作ります。QMainWindow— メニューバー、ステータスバー、中央ウィジェット領域を備えた最上位ウィンドウです。これを継承したクラスに画面構成を入れる方式が標準パターンです。setCentralWidget— ウィンドウの中央領域にウィジェットを配置します。今はボタン1つですが、次回からはレイアウトで組み立てたウィジェットのまとまりが入ります。window.show()— ウィンドウは作っただけでは表示されません。showを呼んで初めて画面に現れます。
app.exec() — イベントループの意味 #
最後の行のapp.exec()が、GUIプログラムとスクリプトの分かれ道です。これまでのPythonスクリプトは上から下へ実行し、終わったら終了していました。しかしGUIアプリは、ユーザーがいつボタンを押すか、いつウィンドウを閉じるかわからないため、終わらずに待ち続ける構造が必要です。
app.exec()はイベントループを開始します。マウスクリック、キーボード入力、ウィンドウのリサイズといったイベントが届くたびに該当ウィジェットへ届け、また次のイベントを待つ無限の繰り返しです。ウィンドウを閉じるとループが終了コードを返して終わり、sys.exit()がそのコードをOSに渡します。
この構造から1つのルールが導かれます。イベントループを長く塞ぐとアプリ全体が止まります。ボタンクリックにつないだ関数が10秒かかる計算をすると、その間は画面の更新もクリックの処理も行われません。この問題と解決策は、#6でスレッドを扱いながら正面から確認します。
まとめ #
今回の核心は3つです。
- Python GUIの選択肢のうちPySide6はQt公式バインディングであり、LGPLライセンスのため商用アプリまで無料で作れます。
- GUIアプリの標準の骨組みは、QApplicationが1つ、QMainWindowを継承したクラス、showの呼び出し、そして
app.exec()です。 app.exec()はイベントを待って配るイベントループを開始し、このループを塞ぐとアプリが止まります。
次回の「PySide6でデスクトップアプリを作る #2 ウィジェットとレイアウト — 画面を組み立てる」では、ボタン1つのウィンドウを超えて、入力欄とリストとボタンをレイアウトで組み立て、ToDoアプリの画面の骨組みを立てます。このアプリはシリーズを通して拡張していく実習の材料です。