mcpField
P
pyqt

pyqt

github.com/codeatcode開発2791 回インストール今日更新
リポジトリ

pyqt について

PyQt と PySide によるデスクトップアプリ開発を支援するスキルハブです。UI コンポーネント、スレッド処理、テスト、スタイルなど、目的別の Qt ガイドへ案内します。

主な機能

  1. 017 つの Qt 専門トピックのガイド
  2. 02MVC と MVVM の設計指針
  3. 03PyQt と PySide の比較
  4. 04PyInstaller と cx_Freeze によるパッケージ化
  5. 05トラブルシューティングと開発の指針

活用シーン

  1. 01Qt の UI アーキテクチャ選定
  2. 02シグナル、ウィジェット、ダイアログの情報収集
  3. 03時間のかかる処理を UI スレッドから分離
  4. 04Qt アプリのテストやパッケージ化

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/codeatcode/oss-ai-skills --skill pyqt

インストール後、Claude Code で/pyqtを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

multimedia/references/
codecs-platforms.md
media-pipeline.md
styling/references/
dark-theme.md
threading/references/
advanced-safety.md
lifecycle-cleanup.md
pool-patterns.md
qt-concurrent.md
testing.md
widgets/references/
event-handling.md
item-views-layouts.md

詳細

PyQt/PySide 開発

PyQt と PySide は、クロスプラットフォームのデスクトップアプリを構築するための Qt アプリケーションフレームワークの Python バインディングです。

サブスキル

詳しくは、各専門サブスキルを参照してください。

スキル説明パス
pyqt-coreシグナル、スロット、タイマー、設定、ファイル I/Ocore/SKILL.md
pyqt-widgetsすべてのウィジェットとレイアウトwidgets/SKILL.md
pyqt-threadingQThread、スレッドプール、並行処理threading/SKILL.md
pyqt-dialogs標準ダイアログとカスタムダイアログdialogs/SKILL.md
pyqt-testingpytest-qt のテストパターンtesting/SKILL.md
pyqt-stylingQSS のスタイル設定とテーマstyling/SKILL.md
pyqt-multimediaオーディオ、ビデオ、カメラ、録画multimedia/SKILL.md

アーキテクチャの選択:Qt における MVC と MVVM

Qt は複数のアーキテクチャパターンをサポートしています。データ表示の複雑さに応じて選択してください。

MVC(Model-View-Controller)

使用する場面:インライン編集を伴うシンプルなデータ表示、標準のアイテムビュー。

  • モデル:QAbstractItemModel のサブクラスがデータを所有します
  • ビュー:QTableView、QTreeView、QListView がデータを表示します
  • コントローラー:ビューに組み込まれています(ヘッダーのクリック、選択の処理)
  • 検証:モデルの setData() メソッドで行います
  • シグナル:モデルが dataChanged()、rowsInserted() を発行してビューに通知します
# Model owns data and validation
class DataModel(QAbstractTableModel):
    def setData(self, index, value, role):
        if not self._validate(value):
            return False
        # update and emit dataChanged
MVVM(Model-View-ViewModel)

使用する場面:異なる形式の同じデータを複数のウィジェットに表示する場合、複雑な UI 状態。

  • モデル:データソース(データベース、API、ファイル)
  • ビューモデル:モデルのデータを表示用に変換し、UI 状態を管理します
  • ビュー:シグナルを介してビューモデルにバインドされた PyQt ウィジェット
  • 検証:モデルを更新する前にビューモデルで行います
  • シグナル:ビューモデルが Property シグナルを公開し、ビューがそれらに接続します
# ViewModel transforms data for display
class UserViewModel(QObject):
    display_name = Property(str, _display_name_changed)
    
    def __init__(self, user_model):
        self._user = user_model
        self._user.nameChanged.connect(self._on_model_changed)
実践テスト:書式設定はどこに置くべきか?

同じデータを異なる書式で2つのウィジェットに表示する場合、書式設定は両方のウィジェットに重複させず、ビューモデルまたはデリゲートに配置します。

悪い例:

# Widget A
label.setText(f"{value:.2f} €")
# Widget B  
label.setText(f"€ {value:,.2f}")

良い例:

# ViewModel provides formatted strings
class PriceViewModel(QObject):
    display_eu = Property(str)
    display_us = Property(str)
    
    def set_price(self, value):
        self._price = value
        self.display_eu_changed.emit()
        self.display_us_changed.emit()
シグナル/スロット:それだけで十分なのはどんな場合か?

以下の場合は、シグナルとスロットだけで十分です。

  • 1つのウィジェットが1つのデータソースを表示する
  • 複雑な派生状態がない(例:「Y と Z の場合にのみ X を表示する」)
  • 検証をモデルの setData() に配置できる
  • ウィジェットなしで UI ロジックをテストする必要がない

以下の場合は、ビューモデル層を追加します。

  • 複数のビューで同じデータを異なる形式で表示する必要がある
  • UI 状態(有効/無効、表示/非表示)が複雑な条件に依存する
  • ウィジェットをインスタンス化せずに表示ロジックをテストしたい

これはどこに属するか?

必要なサブスキルだけを読み込むには、以下の振り分けを使用してください。

目的読み込むサブスキル
シグナル宣言、スロットデコレーター、pyqtSignal/Signal、型付きシグナル、connect()、disconnect()、シグナルチェーンpyqt/core
ウィジェットの構成(複数のウィジェットからカスタムウィジェットを構築すること)、アイテムビュー(QTableView、QTreeView)、デリゲート(QItemDelegate)、イベントフィルターpyqt/widgets
QThread のワーカーオブジェクトパターン、QThreadPool/QRunnable、QExecutor、スレッドセーフ、キャンセル、ブロッキング操作pyqt/threading
標準ダイアログ(QFileDialog、QMessageBox、QInputDialog、QColorDialog、QFontDialog)、カスタム QDialog のパターン、モーダルとモードレスpyqt/dialogs
pytest-qt のフィクスチャ(qtbot)、waitSignal、マウス/キーボードのシミュレーション、ダイアログのテスト、モデル/ビューのテストpyqt/testing
QSS の構文、疑似状態(:hover、:pressed)、ウィジェット固有のスタイル、テーマ設定、プラットフォーム間の違いpyqt/styling
Hub に含めない内容
  • シグナル/スロットの基礎 → pyqt/core(この Hub はルーティングのみを行います)
  • ウィジェット一覧 → pyqt/widgets(この Hub はルーティングのみを行います)
  • QSS の構文と例 → pyqt/styling(この Hub はルーティングのみを行います)
  • レイアウトのコードスニペット → pyqt/widgets(この Hub はルーティングのみを行います)

詳細解説

専門的なトピックについては、以下のサブスキルを読み込んでください。

  • シグナル/スロットの詳細解説 → pyqt/core - シグナル宣言、スロットデコレーター、接続、型付きシグナル
  • ウィジェット、レイアウト、アイテムビュー、イベント処理 → pyqt/widgets - 表示/入力/コンテナーウィジェット、QVBoxLayout/QHBoxLayout/QGridLayout、イベントフィルター、ショートカット
  • QSS によるスタイリングとダークテーマ → pyqt/styling - QSS の構文、疑似状態、ウィジェット固有のスタイル、プロパティベースのスタイリング
  • ダイアログ → pyqt/dialogs - 標準ダイアログ(QFileDialog、QMessageBox、QInputDialog)、カスタム QDialog のパターン、モーダル/モードレス
  • スレッド処理のパターン → pyqt/threading - QThread のワーカーオブジェクトパターン、QThreadPool/QRunnable、スレッドセーフ、キャンセル
  • pytest-qt によるテスト → pyqt/testing - qtbot フィクスチャ、waitSignal、マウス/キーボードのシミュレーション、ダイアログのテスト、モデル/ビューのテスト

PyQt と PySide の比較

機能PyQt5PyQt6PySide6
ライセンスGPLGPLLGPL
Qt のバージョンQt 5Qt 6Qt 6
メンテナンス状況セキュリティ対応のみアクティブアクティブ
シグナル構文pyqtSignalpyqtSignalSignal
スロット構文pyqtSlotpyqtSlotSlot
プロパティ構文pyqtPropertypyqtPropertyProperty
商用利用ライセンスが必要ライセンスが必要無料
QML 登録qmlRegisterType()qmlRegisterType()@QmlElement
それぞれを使う場面
  • PySide6:ほとんどのプロジェクトに推奨(LGPL、Qt Company による公式サポート)
  • PyQt6:GPL との互換性が必要な場合、または既存の PyQt コードベースがある場合
  • PyQt5:レガシープロジェクトのみ(セキュリティ修正のみ)

インストール

PySide6(推奨)
pip install PySide6
PyQt6
pip install PyQt6
PyQt5(レガシー)
pip install PyQt5
追加の依存関係
# System packages (Ubuntu/Debian)
sudo apt install libgl1-mesa-glx libglib2.0-0

# System packages (Fedora)
sudo dnf install mesa-libGL glib2

# System packages (Arch)
sudo pacman -S mesa glib2

基本的なアプリケーション

#!/usr/bin/env python3
import sys
from PySide6.QtWidgets import QApplication, QMainWindow, QLabel
from PySide6.QtCore import Qt

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("My Application")
        self.setGeometry(100, 100, 800, 600)
        
        label = QLabel("Hello, Qt!")
        label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.setCentralWidget(label)

def main():
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec())

if __name__ == "__main__":
    main()

推奨されるプロジェクト構成

my_app/
├── src/
│   ├── __init__.py
│   ├── main.py
│   ├── main_window.py
│   ├── widgets/
│   │   ├── __init__.py
│   │   └── custom_widget.py
│   ├── models/
│   │   └── data_model.py
│   ├── resources/
│   │   ├── icons/
│   │   └── styles/
│   │       └── style.qss
│   └── utils/
│       └── helpers.py
├── tests/
│   └── test_main.py
├── requirements.txt
└── pyproject.toml

クイックリファレンス

コアインポート
from PySide6.QtWidgets import QApplication, QMainWindow, QWidget, QLabel, QPushButton, QLineEdit, QTextEdit, QComboBox, QSpinBox, QCheckBox, QSlider, QProgressBar, QGroupBox, QTabWidget, QStackedWidget, QSplitter, QListWidget, QTreeWidget, QTableWidget, QScrollArea, QToolBar, QStatusBar
from PySide6.QtCore import Qt, QObject, QTimer, QThread, Signal, Slot, Property, QSize, QPoint, QRect, QSettings, QFile, QDir, QUrl, QMimeData, QDateTime
from PySide6.QtGui import QIcon, QPixmap, QImage, QPainter, QPen, QBrush, QColor, QFont, QCursor, QKeySequence, QShortcut
よく使うプロパティ
widget.setEnabled(True)  # Enable/disable
widget.setVisible(False)  # Visibility
widget.setToolTip("Help")  # Tooltip
widget.setObjectName("myButton")  # QSS selector
widget.setProperty("primary", True)  # Custom property

パッケージ化と配布

PyInstaller
# Install
pip install pyinstaller

# Single executable
pyinstaller --onefile --windowed app.py

# With icon and data files
pyinstaller --onefile --windowed --icon=app.ico --add-data "resources:resources" app.py
PyInstaller の Spec ファイル
# app.spec
a = Analysis(
    ['app.py'],
    pathex=[],
    binaries=[],
    datas=[('resources', 'resources')],
    hiddenimports=[],
    hookspath=[],
    runtime_hooks=[],
    excludes=[],
    win_no_prefer_redirects=False,
    win_private_assemblies=False,
    cipher=None,
    noarchive=False,
)
pyz = PYZ(a.pure, a.zipped_data)
exe = EXE(
    pyz,
    a.scripts,
    a.binaries,
    a.zipfiles,
    a.datas,
    [],
    name='MyApp',
    debug=False,
    bootloader_ignore_signals=False,
    strip=False,
    upx=True,
    console=False,
    icon='app.ico',
)
cx_Freeze
# setup.py
from cx_Freeze import setup, Executable

build_options = {
    'packages': ['PySide6'],
    'excludes': ['tkinter'],
    'include_files': [('resources', 'resources')]
}

setup(
    name='MyApp',
    version='1.0',
    description='My Qt Application',
    options={'build_exe': build_options},
    executables=[Executable('app.py', base='Win32GUI', icon='app.ico')]
)
ビルドコマンド
# PyInstaller
pyinstaller --clean app.spec

# cx_Freeze
python setup.py build

# Create distribution
pyinstaller --clean --distpath dist app.spec
配布チェックリスト
  • クリーンな VM(Python がインストールされていない環境)でテストする
  • すべてのアセット(アイコン、QSS、翻訳)が同梱されていることを確認する
  • 実行ファイルのサイズを確認する(必要に応じてデバッグシンボルを削除する)
  • 対象 OS のバージョンでテストする
  • 実行ファイルに署名する(Windows/macOS)
  • インストーラーを作成する(任意:NSIS、Inno Setup、dmgbuild)

ベストプラクティス

アーキテクチャ
  1. UI とビジネスロジックを分離する - MVC または MVVM パターンを使用する
  2. 依存性注入を使用する - 依存関係をコンストラクターに渡す
  3. ウィジェットをステートレスに保つ - 状態はウィジェットではなくモデルに保存する
  4. シグナルを使って疎結合にする - コンポーネント間の通信にはシグナルを使用する
  5. 重いリソースは遅延読み込みする - 必要に応じて画像やデータを読み込む
パフォーマンス
  1. メインスレッドをブロックしない - 時間のかかる処理にはワーカーを使用する
  2. 複雑なアイテム描画にはビューのデリゲートを使用する - 不要に QItemDelegate をサブクラス化しない
  3. UI の更新をまとめて行う - 一括変更中はシグナルをブロックする
  4. ウィジェットを再利用する - 頻繁に作成するウィジェットはプールする
  5. 最適化の前にプロファイリングする - python -m cProfile を使用する
メモリ管理
  1. 親子関係を利用する - Qt が子オブジェクトを自動的にクリーンアップします
  2. 動的なウィジェットには deleteLater() を呼び出す - del を直接使用しない
  3. シグナル/スロットの接続を解除する - 削除する前に接続を解除する
  4. 循環参照を避ける - 必要に応じて弱参照を使用する
  5. tracemalloc で監視する - メモリーリークを見つける
コードの構成
  1. 1ファイルに1クラス - モジュールの役割を絞ります
  2. 関連するウィジェットをまとめる - カスタムウィジェットは個別のモジュールに分けます
  3. 定数を一元管理する - 固定値には列挙型を使用します
  4. 型ヒントを使用する - IDE のサポートやデバッグに役立ちます
  5. 公開 API をドキュメント化する - メソッドやクラスに docstring を記述します

トラブルシューティング

問題原因解決策
UI がフリーズするメインスレッドでブロッキング処理を実行しているワーカースレッドに移動します
ウィジェットへのアクセス時にクラッシュするワーカースレッドから UI にアクセスしている代わりにシグナルを使用します
メモリリークが発生するオブジェクトがクリーンアップされていない親子関係を使用し、deleteLater() を呼び出します
QSS が適用されないセレクターまたは構文が間違っているウィジェットの objectName を確認し、style().polish() を使用します
シグナルが発火しない接続の種類が間違っているシグナルの宣言を確認し、スレッドアフィニティを確認します
CPU 使用率が高いメインスレッドでループが高速に回り続けているタイマーまたはワーカーを使用します
ウィンドウが表示されないshow() の呼び出しがないapp.exec() の前に window.show() を呼び出します
アイコンが読み込まれないパスまたは形式が間違っているバンドルされたアセットには QResource を使用します
デバッグのヒント
# Enable Qt logging
import logging
logging.basicConfig(level=logging.DEBUG)

# Check for unhandled exceptions
import sys
sys.excepthook = lambda exc: print(f"Unhandled: {exc}")

# Print widget hierarchy
print(window.findChildren(QWidget))

# Check signal connections
print(button.receivers(button.clicked))

# Dump QSS errors
app.setStyleSheet("INVALID {")  # Will print parse errors

参考資料

コメント