Qt for Python 6.12 がリリースされ、Qt 6.12 LTS(長期サポート版)と同時に提供を開始しました。これまでで最も大規模なリリースです。Qt 本体の新機能については、一般向けの発表記事をご覧ください。
特に注目すべき点は3つです:
- QML 向けのリアクティブプロパティ: 新しい QtQmlFeatures モジュールにより、Python クラスからシグナルやプロパティの定型コードの大部分を削減できます。
- PySide6 の iOS 対応: 待望の iOS サポートが実現しました。
- EU サイバーレジリエンス法(CRA)を背景としたツールのセキュリティレビュー: Qt for Python の脅威モデリングを実施し、発見された問題を修正したうえで、ソースファイルにセキュリティマーカーを追加しました。
このほか、Qt for Python 全体にわたる多数の修正も含まれています。
QtQmlFeatures:Python 向けのリアクティブプロパティ(テクニカルプレビュー)
Python の値を QML に公開するには、必要以上のコードが必要でした。単一の price プロパティでも、Signal、バッキングフィールド、ゲッター、そして値の変化を確認してシグナルを発行するセッターが必要です。派生値になると、さらに配線が必要になります。
新しい QtQmlFeatures モジュールは、その大部分を不要にします。以下は、price、quantity、派生値の total を持つショッピングカートを従来の方法で書いた例です。
class Cart(QObject): priceChanged = Signal() quantityChanged = Signal() totalChanged = Signal() def __init__(self, parent=None): super().__init__(parent) self._price = 10 self._quantity = 2 self._total = self._price * self._quantity self.priceChanged.connect(self._recompute_total) self.quantityChanged.connect(self._recompute_total) @Property(int, notify=priceChanged) def price(self): return self._price @price.setter def price(self, value): if self._price == value: return self._price = value self.priceChanged.emit() # ...the same again for quantity and total |
同じショッピングカートを新しいデコレーターで書いた例です。大きな価格変動の警告やすべての変更のログ出力も含まれています。従来の方法では、これらを各セッター内に実装する必要がありました。
@auto_properties class Cart(QObject): def __init__(self, parent=None): super().__init__(parent) self.price = 10 self.quantity = 2 @computed("price", "quantity") def total(self) -> int: return self.price * self.quantity @watch("price") def on_price_changed(self, change: Change): if change.new > change.old * 1.5: print(f"warning: price jumped {change.old} -> {change.new}") @effect("price", "quantity") def log_state(self): print(f"cart: {self.price} x {self.quantity}") |
プロパティは引き続き実際の Q_PROPERTY なので、QML から読み書きできます。QML が price を変更すると、ウォッチャーが呼び出され、total が更新され、バインディングが再評価されます。シグナル接続を記述する必要はありません。
4つのデコレーターが機能を担います:
@auto_properties は、単純な self.x = ... の代入を、通知シグナル付きのプロパティに変換します。
@computed は、依存するプロパティが変化したときに更新される、キャッシュ付きの読み取り専用の値を宣言します。
@watch は、プロパティの変更後に実行され、変更前後の値を受け取ります。
@effect は、指定したプロパティのいずれかが変化するたびに実行されます。
リアクティブプロパティのチュートリアルでは、このカートを段階的に構築しています。従来のバージョンから出発し、ステップごとに定型コードを1つずつ取り除いていきます。さらに、以下のサンプルも QtQmlFeatures を使うように更新されました:
新たに2つのサンプルも追加されました:
- QML コンポーネントのロードのサンプル:
load_qml_component を使用して、Python から QML で定義された型をロードする方法を示します。
- Python からの Qt Quick Controls のサンプル:
load_qml_component を使用して Python から Qt Quick Controls の型をロードし、その機能を Python から制御する方法を示します。
iOS サポートでモバイル対応を強化
以前の記事で iOS 対応を進めていることをお伝えしていましたが、このたび iOS サポートを追加 しました(PYSIDE-2352)。ここに至るまでご協力いただいたすべての方々に感謝します。
開始までの手順は3ステップです:
- iOS 用の wheel を取得
iOS には専用の PySide6 と shiboken6 の wheel が必要です。Qt for Python のダウンロードページからダウンロードするか、qtpip で取得できます。
|
$ pip install qtpip
$ qtpip download PySide6 --ios --arch arm64 --platform device
|
iOS シミュレーター用の wheel が必要な場合は、--platform simulator を使用してください。各 wheel は、アーキテクチャーとプラットフォームがそれぞれ1つに対応しています。
- Xcode プロジェクトを生成
新しい pyside6-ios-deploy ツールは、プロジェクトと2つの wheel を受け取り、Xcode プロジェクトを生成します。pyside6-deploy や pyside6-android-deploy と同じ pysidedeploy.spec ファイルを使用します。
|
$ pyside6-ios-deploy /path/to/main.py --name "<AppName>" \ --wheel-pyside=<PySide6 wheel> \ --wheel-shiboken=<shiboken wheel>
|
このツールには macOS のホストが必要です。初回使用時に Python.xcframework を自動でダウンロードします。
- Xcode でビルドして実行
生成された .xcodeproj を開き、シミュレーターまたは接続したデバイスで実行します。署名とプロビジョニングは Xcode で行います。デバイス向けのビルドでは、--team-id で Apple Developer の Team ID を指定してください。
詳細は pyside6-ios-deploy のドキュメントをご覧ください。また、専用記事の Python モバイルアプリ開発:PySide6 の iOS 対応も、最新の機能とオプションを反映して更新されています。
デプロイはよりスムーズに、より多くのプラットフォームをサポート
今回のリリースでは、デプロイ機能に多くの変更が加えられました。
pyside6-deploy
pyproject.toml 内の [tool.pyside6.deploy] セクションで設定できるようになりました(PYSIDE-1612)。
pyside6-deploy、pyside6-android-deploy、pyside6-project が --no-install を受け付けるようになり、ビルド依存関係の自動インストールをスキップできます。
- ⚠️ 動作の変更:
pyside6-deploy はデフォルトでスタンドアロンパッケージを作成するようになりました(PYSIDE-3459)。以前のデフォルト動作が必要な場合は --onefile を使用してください。
Android
- NDK が r28c に更新され、limited API の wheel タグが cp310 に引き上げられました。
- ホストの Python 3.10〜3.14 をサポートし、生成されるバイナリは 16 KB メモリページ のデバイスに対応しています(PYSIDE-3425)。
- ツールは、満たされていない前提条件をハングせずに報告するようになりました。また、QtQmlFeatures を使用するアプリケーションも確実にデプロイできるようになりました。
- 手動のクロスコンパイルに代わり、ビルド済みの CPython を使用するようになりました(PYSIDE-3426)。
- JDK の検索と、macOS における NDK の隔離(quarantine)の問題を修正しました。
セキュリティ強化
サイバーレジリエンス法(CRA)の報告義務が適用されるようになり、セキュリティへの関心がさらに高まっています。Qt for Python にとってセキュリティは常に優先事項です。6.12 では、攻撃者の視点からツールを見直し、Qt for Python 全体について完全な脅威分析を実施しました。
開発者の使用体験に影響する主な変更点は次のとおりです:
- Android デプロイ: wheel の展開時に発生しうる zip slip の脆弱性を修正しました。また、ダウンロードした NDK とコマンドラインツールは、展開前に検証されるようになりました。
- pyside6-qml: QML ソースと同じ場所にある Python ファイルをインポートする前に警告を表示するようになりました。
- pyside6-deploy: Windows における潜在的なシェルインジェクションの脆弱性を修正しました。
- シリアライズ: QDataStream と QSettings から、単純ではないクラスを逆シリアライズする処理をブロックするようになりました。
- Qt Widgets Designer: Python プラグインを読み込む前に、一度限りの確認を求めるようになりました。
- QUiLoader:
loadUiType() はもはやパス内の pyside6-uic バイナリを検索しません。この機能を使うには、キーワード引数で明示的に有効化する必要があり、警告も表示されます。ビルド時に pyside6-uic を実行する方法への移行を推奨します。
- Shiboken6: doxygen ドキュメントの生成時に、libxml 2.13 以降では XML を XML_PARSE_NO_XXE で解析し、それ以前のバージョンでは実体展開を無効にするようになりました。
⚠️ SBOM ドキュメントは、まだ準備が整っていないため、今後のリリースで追加する予定です。
Qt WebEngine と Qt PDF の配布形態の変更
Qt WebEngine をご利用の方は、リリースを分離する計画を発表した記事をすでにお読みかもしれません。これに従い、開発者が PySide のリリースと同時に最新版を入手できるようにします。
Wheel にはモジュールのバイナリが含まれるため、Qt の完全なバージョンと Chromium のバージョンを組み合わせたバージョン番号を使用します。例えば、Qt WebEngine 6.140 の場合、6.12 のリリースではバージョン 6.12.0.140 になります。これにより、Qt のバグ修正リリースと、WebEngine 向けの新しい Chromium リリースの両方に対して、新しい wheel を提供できるようになります。
Qt PDF モジュールの依存関係も、この新しい構成の影響を受けるため、同じ方式に従います。
Python新バージョンと改善
- Python 3.15: 初期サポートを追加しました(PYSIDE-3221)。Shiboken は 3.15 向けに、条件分岐を使ってモジュール初期化コードを生成するようになりました。
- 型ヒント: QDate と QDateTime が Python の datetime 型を受け付けるようになりました(PYSIDE-3038)。また、
__copy__ の戻り値の型注釈を修正しました(PYSIDE-3338)。
- 新しい API: QBitArray.fromBits(PyBuffer) と QBitArray.bits() を追加しました(PYSIDE-3335)。QMetaObject::className() は、ネストされたクラスについて
Outer.Inner のような修飾名を返すようになりました(PYSIDE-3294)。
- 診断: 未知のシグナルパラメーターに遭遇した際に警告を表示するようになりました(PYSIDE-3364)。
- フリースレッド Python: Quansight による PySide のフリースレッド対応への取り組みが進行中です。近いうちにお知らせできることを期待しています。
安定性の修正
invisibleRootItem() 経由でデータを設定した後に clear() を呼び出すと発生していた、QStandardItemModel のメモリリークを修正しました(PYSIDE-3365)。
QAbstractItemModel.data() からシーケンスプロトコルを実装するクラスを返すとクラッシュする問題を修正しました(PYSIDE-3423)。
QtWebEngineCore.ResourceRequestBody のラッパーを取得する際のクラッシュを修正しました(PYSIDE-3409)。
- 不適切な親子設定により古くなった
QLayoutItem ポインターが原因で発生していたクラッシュを修正しました(PYSIDE-3469)。
__ で始まるプライベートメソッドへの、派生クラスからの接続を修正しました(PYSIDE-3376)。
- ヘッダーのパッケージ化とインクルードパスに影響していた CMake ビルドの回帰を修正しました(PYSIDE-3306)。
⚠️ QTreeWidgetItem、QTableWidgetItem、QListWidgetItem のコピーコンストラクターを削除しました(PYSIDE-3367)。
Shiboken6 の更新
- フリースレッドビルドにおける GIL の使用を指定する、タイプシステム属性を追加しました(PYSIDE-2221)。
QEventLoop.exec() のようなブロッキング関数に、関数の変更指定でフラグを付けられるようになりました(PYSIDE-3356)。
- 関連する Nuitka のバグが修正されたため、配布されたケースでもマルチフェーズのモジュール初期化コードを生成するようになりました(PYSIDE-3155)。
- shiboken6 の
ObjectType/Object 型の __module__ 属性を修正しました(PYSIDE-3336)。
- shibokenmodule に、親子関係を調べるための再帰的な dump() を追加しました(PYSIDE-3469)。
Qt for Python の入手方法
最新版は、以下のコマンドで入手できます:
pip install pyside6
uv を使用している場合は、プロジェクトに追加して環境を同期します:
uv add pyside6
uv sync
商用ユーザーの場合は、以下を使用してください:
qtpip install pyside6
ご意見をお聞かせください
今後も、新しい実験的サポート、機能、Python モジュールとの統合を試していきたいと考えています。次に何に取り組むべきか、ぜひお聞かせください。メッセージをお寄せいただくか、Qt Bug Tracker で提案を登録してください
本リリースをお楽しみいただければ幸いです。コミュニティプラットフォームにもぜひお立ち寄りいただき、問題があれば バグレポートでお知らせください。