PySide6
Qt 6ベースのPython用クロスプラットフォームGUIフレームワーク。Qt社公式サポートでLGPLライセンス。商用利用が可能で、PyQtより企業導入しやすい。豊富なウィジェットとモダンなUI開発が特徴。
フレームワーク
PySide6
概要
PySide6は、Qt 6ベースのPython用クロスプラットフォームGUIフレームワークです。Qt社による公式サポートとLGPLライセンスにより、商用利用が可能で、PyQtより企業導入しやすいのが特徴です。豊富なウィジェットとモダンなUI開発機能を提供します。
詳細
PySide6は、2025年にPythonデスクトップ開発の主流選択肢として確立されています。Qt 6対応により最新UI機能とパフォーマンス向上を実現し、商用プロジェクトでの採用率がPyQtを上回っています。特に金融・科学分野での需要が増加しています。
Qt社(The Qt Company)による公式サポートを受けており、LGPLライセンスにより商用利用への制約が少なく、企業での採用が進んでいます。Qt DesignerによるビジュアルUIデザイン、QMLとの統合、豊富なウィジェットライブラリなど、プロフェッショナルなデスクトップアプリケーション開発に必要な機能が包括的に提供されています。
Maya、FreeCAD、Eric IDE、Anki、Spyderなど、多くの著名アプリケーションでPySide6が採用されており、Python開発者がプロフェッショナルなデスクトップアプリケーションを構築する際の標準的な選択肢となっています。
メリット・デメリット
メリット
- 公式サポート: Qt社による公式サポートと継続的な開発
- ライセンスの柔軟性: LGPLライセンスで商用利用しやすい
- Pythonらしい API: Pythonic なインターフェースで学習しやすい
- Qt 6 の最新機能: モダンなUI機能とパフォーマンス
- 豊富なウィジェット: デスクトップアプリに必要な全機能
- Qt Designer統合: ビジュアルUIデザインツール
- QML サポート: 宣言的UIと高度なアニメーション
- クロスプラットフォーム: Windows、macOS、Linux対応
- 豊富なドキュメント: 詳細なドキュメントとサンプル
デメリット
- アプリサイズ: Qt依存によりバイナリサイズが大きくなる
- メモリ使用量: リッチなGUIによりメモリ消費が多い
- 配布の複雑さ: 依存関係の管理とパッケージング
- 学習コスト: Qt固有の概念とシグナル・スロット理解が必要
- パッケージサイズ: pipインストール時のダウンロードサイズが大きい
主要リンク
書き方の例
Hello Worldアプリケーション
# hello_world.py
import sys
from PySide6.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout, QLabel, QPushButton
from PySide6.QtCore import Qt
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("PySide6 Hello World")
self.setGeometry(100, 100, 300, 200)
# セントラルウィジェット
central_widget = QWidget()
self.setCentralWidget(central_widget)
# レイアウト
layout = QVBoxLayout(central_widget)
# ラベル
self.label = QLabel("Hello, PySide6!", self)
self.label.setAlignment(Qt.AlignCenter)
layout.addWidget(self.label)
# ボタン
button = QPushButton("クリックしてください", self)
button.clicked.connect(self.on_button_clicked)
layout.addWidget(button)
self.click_count = 0
def on_button_clicked(self):
self.click_count += 1
self.label.setText(f"クリック回数: {self.click_count}")
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())
シグナルとスロットの活用
# signals_slots.py
import sys
from PySide6.QtWidgets import (QApplication, QMainWindow, QWidget,
QVBoxLayout, QHBoxLayout, QLabel,
QPushButton, QSlider, QLineEdit)
from PySide6.QtCore import Qt, QTimer, Signal, QObject
class DataModel(QObject):
# カスタムシグナル
value_changed = Signal(int)
message_updated = Signal(str)
def __init__(self):
super().__init__()
self._value = 0
self._timer = QTimer()
self._timer.timeout.connect(self.update_value)
def start_auto_update(self):
self._timer.start(1000) # 1秒間隔
def stop_auto_update(self):
self._timer.stop()
def update_value(self):
self._value += 1
self.value_changed.emit(self._value)
self.message_updated.emit(f"自動更新値: {self._value}")
def set_manual_value(self, value):
self._value = value
self.value_changed.emit(self._value)
self.message_updated.emit(f"手動設定値: {self._value}")
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("シグナル・スロットの例")
self.setGeometry(100, 100, 400, 300)
# データモデル
self.model = DataModel()
# UIセットアップ
self.setup_ui()
# シグナル接続
self.model.value_changed.connect(self.on_value_changed)
self.model.message_updated.connect(self.on_message_updated)
def setup_ui(self):
central_widget = QWidget()
self.setCentralWidget(central_widget)
layout = QVBoxLayout(central_widget)
# 値表示ラベル
self.value_label = QLabel("値: 0")
layout.addWidget(self.value_label)
# メッセージラベル
self.message_label = QLabel("メッセージ: なし")
layout.addWidget(self.message_label)
# スライダー
slider_layout = QHBoxLayout()
slider_layout.addWidget(QLabel("手動設定:"))
self.slider = QSlider(Qt.Horizontal)
self.slider.setRange(0, 100)
self.slider.valueChanged.connect(self.model.set_manual_value)
slider_layout.addWidget(self.slider)
layout.addLayout(slider_layout)
# ボタン
button_layout = QHBoxLayout()
self.start_button = QPushButton("自動更新開始")
self.start_button.clicked.connect(self.model.start_auto_update)
button_layout.addWidget(self.start_button)
self.stop_button = QPushButton("自動更新停止")
self.stop_button.clicked.connect(self.model.stop_auto_update)
button_layout.addWidget(self.stop_button)
layout.addLayout(button_layout)
def on_value_changed(self, value):
self.value_label.setText(f"値: {value}")
# スライダーの更新(シグナルループを避けるため一時的に切断)
self.slider.blockSignals(True)
self.slider.setValue(min(value, 100))
self.slider.blockSignals(False)
def on_message_updated(self, message):
self.message_label.setText(f"メッセージ: {message}")
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())
Qt Designerとの統合
# designer_ui.py
import sys
from PySide6.QtWidgets import QApplication, QMainWindow
from PySide6.QtUiTools import QUiLoader
from PySide6.QtCore import QFile, QIODevice
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.load_ui()
self.setup_connections()
def load_ui(self):
# .uiファイルを読み込み
ui_file = QFile("mainwindow.ui")
if ui_file.open(QIODevice.ReadOnly):
loader = QUiLoader()
self.ui = loader.load(ui_file, self)
ui_file.close()
# UIをメインウィンドウに設定
self.setCentralWidget(self.ui)
else:
print("UIファイルの読み込みに失敗しました")
def setup_connections(self):
# UI要素への参照を取得し、シグナルを接続
if hasattr(self.ui, 'pushButton'):
self.ui.pushButton.clicked.connect(self.on_button_clicked)
if hasattr(self.ui, 'lineEdit'):
self.ui.lineEdit.textChanged.connect(self.on_text_changed)
def on_button_clicked(self):
if hasattr(self.ui, 'label'):
self.ui.label.setText("ボタンがクリックされました!")
def on_text_changed(self, text):
if hasattr(self.ui, 'label_2'):
self.ui.label_2.setText(f"入力: {text}")
# 代替案: .pyファイルに変換されたUIを使用
# pyside6-uic mainwindow.ui -o ui_mainwindow.py
# from ui_mainwindow import Ui_MainWindow
#
# class MainWindow(QMainWindow):
# def __init__(self):
# super().__init__()
# self.ui = Ui_MainWindow()
# self.ui.setupUi(self)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())
QMLとの統合
# qml_integration.py
import sys
import os
from PySide6.QtGui import QGuiApplication
from PySide6.QtQml import QmlElement, qmlRegisterType
from PySide6.QtQuick import QQuickView
from PySide6.QtCore import QObject, Signal, Slot, Property
# QMLで使用するためのPythonクラス
QML_IMPORT_NAME = "DataModel"
QML_IMPORT_MAJOR_VERSION = 1
@QmlElement
class DataModel(QObject):
# シグナル
dataChanged = Signal(str)
def __init__(self):
super().__init__()
self._data = "初期データ"
# プロパティ
def get_data(self):
return self._data
def set_data(self, data):
if self._data != data:
self._data = data
self.dataChanged.emit(data)
data = Property(str, get_data, set_data, notify=dataChanged)
# スロット(QMLから呼び出し可能)
@Slot()
def update_data(self):
import random
new_data = f"ランダムデータ: {random.randint(1, 100)}"
self.set_data(new_data)
@Slot(str, result=str)
def process_input(self, input_text):
return f"処理済み: {input_text.upper()}"
def main():
app = QGuiApplication(sys.argv)
# QMLエンジンにPythonクラスを登録
qmlRegisterType(DataModel, "DataModel", 1, 0, "DataModel")
# QMLファイルを読み込み
view = QQuickView()
view.setSource("main.qml")
view.show()
return app.exec()
if __name__ == "__main__":
sys.exit(main())
// main.qml
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import DataModel 1.0
ApplicationWindow {
visible: true
width: 400
height: 300
title: "PySide6 QML統合"
DataModel {
id: dataModel
onDataChanged: {
resultText.text = "データ更新: " + data
}
}
ColumnLayout {
anchors.centerIn: parent
spacing: 20
Text {
id: resultText
text: "データ: " + dataModel.data
font.pixelSize: 16
Layout.alignment: Qt.AlignHCenter
}
TextField {
id: inputField
placeholderText: "テキストを入力"
Layout.fillWidth: true
Layout.maximumWidth: 300
Layout.alignment: Qt.AlignHCenter
}
Row {
spacing: 10
Layout.alignment: Qt.AlignHCenter
Button {
text: "データ更新"
onClicked: dataModel.update_data()
}
Button {
text: "入力処理"
onClicked: {
var result = dataModel.process_input(inputField.text)
resultText.text = result
}
}
}
}
}
データベース操作
# database_example.py
import sys
import sqlite3
from PySide6.QtWidgets import (QApplication, QMainWindow, QWidget,
QVBoxLayout, QHBoxLayout, QTableWidget,
QTableWidgetItem, QPushButton, QLineEdit,
QLabel, QMessageBox)
from PySide6.QtCore import Qt
class DatabaseApp(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("PySide6 データベース操作")
self.setGeometry(100, 100, 600, 400)
# データベース初期化
self.init_database()
# UI作成
self.setup_ui()
# データ読み込み
self.load_data()
def init_database(self):
self.conn = sqlite3.connect("users.db")
cursor = self.conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL,
age INTEGER
)
""")
self.conn.commit()
def setup_ui(self):
central_widget = QWidget()
self.setCentralWidget(central_widget)
layout = QVBoxLayout(central_widget)
# 入力フォーム
form_layout = QHBoxLayout()
form_layout.addWidget(QLabel("名前:"))
self.name_input = QLineEdit()
form_layout.addWidget(self.name_input)
form_layout.addWidget(QLabel("メール:"))
self.email_input = QLineEdit()
form_layout.addWidget(self.email_input)
form_layout.addWidget(QLabel("年齢:"))
self.age_input = QLineEdit()
form_layout.addWidget(self.age_input)
add_button = QPushButton("追加")
add_button.clicked.connect(self.add_user)
form_layout.addWidget(add_button)
layout.addLayout(form_layout)
# テーブル
self.table = QTableWidget()
self.table.setColumnCount(4)
self.table.setHorizontalHeaderLabels(["ID", "名前", "メール", "年齢"])
layout.addWidget(self.table)
# ボタン
button_layout = QHBoxLayout()
refresh_button = QPushButton("更新")
refresh_button.clicked.connect(self.load_data)
button_layout.addWidget(refresh_button)
delete_button = QPushButton("削除")
delete_button.clicked.connect(self.delete_user)
button_layout.addWidget(delete_button)
layout.addLayout(button_layout)
def add_user(self):
name = self.name_input.text().strip()
email = self.email_input.text().strip()
age_text = self.age_input.text().strip()
if not name or not email:
QMessageBox.warning(self, "警告", "名前とメールは必須です")
return
try:
age = int(age_text) if age_text else None
cursor = self.conn.cursor()
cursor.execute(
"INSERT INTO users (name, email, age) VALUES (?, ?, ?)",
(name, email, age)
)
self.conn.commit()
# 入力フィールドクリア
self.name_input.clear()
self.email_input.clear()
self.age_input.clear()
# テーブル更新
self.load_data()
except ValueError:
QMessageBox.warning(self, "エラー", "年齢は数字で入力してください")
except sqlite3.IntegrityError:
QMessageBox.warning(self, "エラー", "このメールアドレスは既に存在します")
def load_data(self):
cursor = self.conn.cursor()
cursor.execute("SELECT id, name, email, age FROM users")
rows = cursor.fetchall()
self.table.setRowCount(len(rows))
for row_index, row_data in enumerate(rows):
for col_index, col_data in enumerate(row_data):
item = QTableWidgetItem(str(col_data if col_data is not None else ""))
self.table.setItem(row_index, col_index, item)
def delete_user(self):
current_row = self.table.currentRow()
if current_row < 0:
QMessageBox.warning(self, "警告", "削除する行を選択してください")
return
user_id = self.table.item(current_row, 0).text()
reply = QMessageBox.question(
self, "確認",
f"ユーザーID {user_id} を削除しますか?",
QMessageBox.Yes | QMessageBox.No
)
if reply == QMessageBox.Yes:
cursor = self.conn.cursor()
cursor.execute("DELETE FROM users WHERE id = ?", (user_id,))
self.conn.commit()
self.load_data()
def closeEvent(self, event):
self.conn.close()
event.accept()
if __name__ == "__main__":
app = QApplication(sys.argv)
window = DatabaseApp()
window.show()
sys.exit(app.exec())
プロジェクト設定と実行
# 仮想環境作成(推奨)
python -m venv pyside6_env
source pyside6_env/bin/activate # Linux/macOS
# または
pyside6_env\Scripts\activate # Windows
# PySide6インストール
pip install PySide6
# Qt Designer使用の場合
designer # Qt Designerを起動
# .uiファイルを.pyファイルに変換
pyside6-uic mainwindow.ui -o ui_mainwindow.py
# リソースファイル(.qrc)の変換
pyside6-rcc resources.qrc -o resources_rc.py
# アプリケーション実行
python hello_world.py
# スタンドアロン実行ファイル作成(PyInstaller使用)
pip install pyinstaller
pyinstaller --onefile --windowed hello_world.py
# requirements.txt作成
pip freeze > requirements.txt
プロジェクト構造例
my_pyside6_app/
├── main.py # メインアプリケーション
├── ui/
│ ├── __init__.py
│ ├── mainwindow.ui # Qt Designerファイル
│ └── ui_mainwindow.py # 変換されたUIファイル
├── resources/
│ ├── icons/
│ ├── images/
│ └── resources.qrc # リソースファイル
├── models/
│ ├── __init__.py
│ └── data_model.py # データモデル
├── views/
│ ├── __init__.py
│ └── custom_widgets.py # カスタムウィジェット
├── utils/
│ ├── __init__.py
│ └── database.py # データベースユーティリティ
├── requirements.txt
└── README.md