Skip to main content

Qt for Python 6.12 正式发布!

评论

Qt for Python 6.12 随 Qt 6.12 LTS 一同发布,是近年来规模最大的一次版本更新。如果尚未了解 Qt 新版本的变化,请查阅整体发布公告。

以下几项尤为突出:

  • 面向 QML 的响应式属性:全新的 QtQmlFeatures 模块可省去 Python 类中大部分信号与属性的模板代码。
  • PySide6 支持 iOS:期待已久的 iOS 支持现已推出。
  • 由《欧盟网络弹性法案》(CRA)推动的工具链安全审查:我们对 Qt for Python 进行了威胁建模,修复了发现的问题,并在源文件中添加了安全标记。
  • Python 3.15:基于此前的某个候选发布版(RC)提供初步支持;自由线程(free-threaded)版 Python 的支持仍在研究中。

此外,本次发布还包含 Qt for Python 各部分的大量 bug 修复。

QtQmlFeatures:面向 Python 的响应式属性(技术预览版)

将一个 Python 值暴露给 QML 所需的代码比实际应有的要多。仅一个 price 属性,就需要一个 Signal、一个后端字段、一个 getter,以及一个检查值的变更并发出信号的 setter。对于派生值还需要更多额外的关联代码。

全新的 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

 

下面是使用新装饰器实现的同一个购物车代码,同时它还会在价格大幅上涨时发出警告,并记录每一次变更。传统写法则需要在每个 setter 中分别实现这些逻辑。

@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 随之更新,绑定也会刷新。整个过程无需编写任何信号连接的代码。

以下四个装饰器完成了这些工作:

  • @auto_properties 将普通的 self.x = ... 赋值转换为带通知信号的属性。
  • @computed 声明一个带缓存的只读值,依赖项发生变化时自动刷新。
  • @watch 在属性变化后运行,并提供变化前后的值。
  • @effect 在所列属性中任意一个发生变化时运行。

响应式属性教程会逐步构建这个购物车示例:从传统写法出发,每一步去掉一部分模板代码。此外,以下示例已更新为使用 QtQmlFeatures:

此外还新增了两个示例:

  • QML 组件加载示例:演示如何通过 load_qml_component 从 Python 加载 QML 定义的类型
  • 在 Python 中使用 Quick Controls:演示如何通过 load_qml_component 从 Python 加载 QtQuick Controls 类型,并在 Python 中驱动其功能

iOS 支持:进一步增强移动端兼容性

我们此前提到 iOS 支持正在推进,现在很高兴地宣布:iOS 支持已经加入(PYSIDE-2352)。感谢一路走来为达成这一里程碑做出贡献的所有人。

入门只需三步:

  1. 获取 iOS wheel 包。

    iOS 需要专用的 PySide6 和 shiboken6 wheel 包,可从 Qt for Python 下载页面下载,也可通过 qtpip 获取:

     

    $ pip install qtpip

    $ qtpip download PySide6 --ios --arch arm64 --platform device

    使用 --platform simulator 可改为获取适用于 iOS 模拟器的 wheel 包。每个 wheel 包仅针对一种架构和一个平台。


  2. 生成 Xcode 工程。

    全新的 pyside6-ios-deploy 工具会根据项目和这两个 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。

  3. 在 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 deploy 支持 --no-install 参数,可跳过构建依赖项的自动安装。
  • ⚠️ 行为变更:pyside6-deploy 现在默认生成独立(standalone)包(PYSIDE-3459)。如需沿用之前的默认行为,请使用 --onefile。

Android

  • NDK 已更新至 r28c,limited API wheel 标签提升至 cp310。
  • 支持 3.10 至 3.14 的主机 Python 版本,生成的二进制文件兼容使用 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 上潜在的 shell 注入问题。
  • 序列化:现在会阻止从 QDataStream 和 QSettings 反序列化非简单类。
  • Qt Widgets Designer:加载 Python 插件前,现在会要求一次性确认。
  • QUiLoader:loadUiType() 不再在系统路径中搜索 pyside6-uic 二进制文件。现在需通过关键字参数显式启用,并会触发警告。我们建议尽量不再使用该函数,改为在构建时运行 pyside6-uic。
  • Shiboken6:生成 doxygen 文档时,对 libxml ≥ 2.13 使用 XML_PARSE_NO_XXE 解析 XML,对更早版本则禁用实体扩展。

⚠️ 软件物料清单(SBOM)文档尚未就绪,我们将在后续版本中添加。

Qt WebEngine 与 Qt PDF 的全新分发方式

如果使用 Qt WebEngine,可能已经读过宣布发布节奏拆分的博客文章。为此,我们采用相同的配置,确保开发者能随 PySide 版本同步获得最新版本。

由于 wheel 包包含各模块的二进制文件,我们将采用的方案是:wheel 包的版本号由 Qt 完整版本号加 Chromium 版本号组合而成。这意味着,对于 Qt WebEngine 6.140,我们会在 6.12 中发布版本号为 6.12.0.140 的包,从而既能随 Qt 的缺陷修复版本发布新的 wheel 包,也能随 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)
  • 修复影响头文件打包和 include 路径的 CMake 构建回归问题(PYSIDE-3306)

⚠️ 已移除 QTreeWidgetItem、QTableWidgetItem 和 QListWidgetItem 的拷贝构造函数(PYSIDE-3367)。

Shiboken6 更新

  • 新增类型系统属性,用于在自由线程构建中控制 GIL 的使用(PYSIDE-2221)
  • 现在可以通过函数修改(function modification)标记 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 上提交建议 👍。

希望大家喜欢这次发布。与往常一样,欢迎访问我们的社区平台;如遇问题,请提交缺陷报告告知我们。

 

评论

Subscribe to our blog

立即体验 Qt 6.12!

点击此处下载最新版本:www.qt.io/download

Qt 6.12 LTS 现已发布,为应用程序开发者和设备制造商带来了新功能和改进。

我们正在招聘

点击此处查看所有职位空缺并关注我们的Instagram账号,了解成为#QtPeople的感受。