注意事项

API变更

PySide6 的目标之一是与 PyQt 实现 API 兼容,但有一些例外情况。

最新的考虑和已知问题也将在开发者笔记中报告。

__hash__() 函数返回值

为类 PySide6.QtCore.QDatePySide6.QtCore.QDateTimePySide6.QtCore.QTimePySide6.QtCore.QUrl 返回的哈希值将基于它们的字符串表示,因此具有相同值的对象将产生相同的哈希值。

QString

修改了改变QString参数内容的方法和函数,以接收不可变的Python Unicode(或str)并返回另一个Python Unicode/str作为修改后的字符串。

以下方法的返回类型已按此方式修改:

类: QAbstractSpinBox, QDateTimeEdit, QDoubleSpinBox, QSpinBox, QValidator

  • fixup(string): string

  • validate(string, int): [QValidator.State, string, int]

类: QDoubleValidator, QIntValidator, QRegExpValidator

  • validate(string, int): [QValidator.State, string, int]

类: QClipboard

  • text(string, QClipboard.Mode mode=QClipboard.Clipboard): [string, string]

类: QFileDialog

与PyQt的getOpenFileNameAndFilter()getOpenFileNamesAndFilter()getSaveFileNameAndFilter()不同,PySide修改了原始方法以返回一个元组。

  • getOpenFileName(QWidget parent=None, str caption=None, str dir=None, str filter=None, QFileDialog.Options options=0): [string, filter]

  • getOpenFileNames(QWidget parent=None, str caption=None, str dir=None, str filter=None, QFileDialog.Options options=0): [list(string), filter]

  • getSaveFileName(QWidget parent=None, str caption=None, str dir=None, str filter=None, QFileDialog.Options options=0): [string, filter]

类: QWebPage

  • javaScriptPrompt(QWebFrame, string, string): [bool, string]

类: QFontMetrics 和 QFontMetricsF

他们添加了两个新方法。两者都接受一个单字符的字符串并将其转换为QChar(以调用C++对应的方法):

  • widthChar(string)

  • boundingRectChar(string)

QTextStream

在这个类中,为了避免与原生Python函数冲突,进行了一些重命名。 它们是:bin_()hex_()oct_()。 唯一的修改是添加了‘_’字符。

QVariant

由于QVariant已被移除,任何期望它的函数都可以接收任何Python对象(None是无效的QVariant)。 同样的规则在返回某些内容时也适用:返回的QVariant将被转换为其原始的Python对象类型。

当一个方法期望一个QVariant::Type时,程序员可以使用字符串(类型名称)或类型本身。

qApp “宏”

QtWidgets 的 C++ API 提供了一个名为 qApp 的宏,它大致扩展为 QtWidgets::QApplication->instance()

在PySide中,我们尝试创建一种类似宏的体验。 为此,qApp变量被实现为一个普通变量, 它存在于内置模块中。 导入PySide6后,你可以立即使用qApp

作为“如果未创建应用程序则创建应用程序”操作的有用快捷方式,我们推荐:

qApp or QtWidgets.QApplication()

或者如果你想检查是否存在一个,只需使用真值:

if qApp:
    # do something if an application was created
    pass

None进行比较也是可能的,但稍微有些过度指定。

测试支持

出于测试目的,您也可以通过调用以下方法来删除应用程序:

qApp.shutdown()

至于5.14.2,这目前是一个实验性功能,尚未经过全面测试。

嵌入状态

在嵌入式模式下,预先在C++中创建的应用程序对象没有Python包装器。 qApp变量是与包装的应用程序一起创建的。 因此,在嵌入式模式下,qApp不存在。 请注意,您始终可以使用QtWidgets.QApplication.instance()来代替。

被放弃的替代方案

我们还尝试了一种替代实现,使用了一个qApp()函数,它更加pythonic且没有问题,但许多人更喜欢qApp宏的简洁性,所以这里提供了它。

丰富的比较

在PySide类的tp_richcompare实现中存在一个长期存在的错误。

  • 当一个类没有实现它时,将使用object的默认实现。 这实现了==!=,就像is操作符一样。

  • 当一个类只实现了一个函数如 < 时,默认实现被禁用,像 obj in sequence 这样的表达式会因 NotImplemented 而失败。

此疏忽已在版本5.15.1中修复。

功能

在Qt for Python中,我们首次开始支持更加Python化的用户界面。 通过一个特殊的导入语句,您可以开启替换Python解释器某些方面的功能。 这可以通过在PySide6导入之后立即使用导入语句来实现。

蛇形命名法

使用以下语句:

from __feature__ import snake_case

当前模块中的所有方法都已从camelCase切换到snake_case。 单个大写字母被替换为下划线和小写字母。

true_property

使用以下语句:

from __feature__ import true_property

所有在Qt6文档中标记为属性的getter和setter函数都被替换为Python属性对象。属性也会在类的相应QMetaObject中列出。

两个功能的示例

一些用于Python的Qt代码片段可能如下所示:

self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)

选择了上述功能后,这表示:

self.table.horizontal_header().section_resize_mode = QHeaderView.Stretch

此外,属性也可以直接在Shiboken中为非Qt库声明,参见property-declare

更多关于功能

关于功能的详细信息可以在这里找到:为什么我们有一个__feature__?

工具

Qt for Python 提供了一些 Qt 工具:

  • pyside6-rcc: Qt 资源编译器。这是一个命令行工具,用于将包含二进制数据(例如图像)的 .qrc 文件编译为可执行的 Python 代码(参见 using_qrc_files)。

  • pyside6-uic: Qt 用户界面编译器。这是一个命令行工具,用于将包含基于 Qt Widget 表单设计的 .ui 文件编译为可执行的 Python 代码(参见 using_ui_files)。

  • pyside6-assistant: Qt 帮助查看器。这是一个图形工具,可用于从 Qt 压缩帮助文件(.qhc)中查看 Qt 文档。目前,仅提供不带文档集的二进制文件以减少轮子大小。有关构建文档的信息,请参阅 Building the documentation

  • pyside6-designer: Qt 用户界面设计器。这是一个图形工具,用于创建基于 Qt Widget 的表单设计并使用自定义小部件(参见 using_ui_files, Custom Widgets in Qt Widgets Designer)。

新的Python枚举

使用新枚举的动机

长期以来,只有Shiboken枚举,它们尽可能精确地模仿了Qt中现有的枚举。这些枚举是小类,也继承自int。

同时,Python的枚举类型已经发展了多年。它们已成为现代Python的自然组成部分。该实现完美地模拟了Python用户的需求。因此,停止在同一应用程序中使用两种不同的枚举实现,而是到处使用新的Python实现是理所当然的。

现有工作

从PySide 6.3开始的新枚举,用Python变体替换了Shiboken枚举,这些变体将内置枚举与QEnum部分中展示的现有QEnum“宏”进行了协调。

PySide中的枚举行为

PySide 6.3 中,旧的和新的枚举有双重实现,其中默认是旧的枚举。 新的枚举方法在 PySide 6.4 中是默认的,并且在 PySide 6.6 中成为强制性的。 存在环境变量 PYSIDE6_OPTION_PYTHON_ENUM,其默认值为“1”。也可以通过指定不同的标志来选择变体,但不再支持“0”的值(关闭)。

关闭某些枚举功能的可用选项可以在 枚举功能集 部分找到。

新旧枚举之间的区别

Python枚举和Shiboken枚举在某种程度上是相互兼容的。 微小的差异在于限制:

  • Python 枚举不能相互继承,而 Shiboken 枚举可以

  • Python 枚举不允许未定义的值,Shiboken 枚举允许

  • Python 枚举总是需要恰好一个参数,Shiboken 枚举有一个默认的零值

  • Python 枚举很少继承自 int,Shiboken 枚举总是继承自 int

更明显的是标志之间的差异,如下所示:

Shiboken 标志构造函数示例在 PySide 6.3 之前已经存在:

flags = Qt.Alignment()
enum = Qt.AlignmentFlag

使用枚举快捷方式如

Qt.AlignLeft = Qt.AlignmentFlag.AlignLeft
Qt.AlignTop  = Qt.AlignmentFlag.AlignTop

在 PySide 6.3 中,这些快捷方式和标志不再存在(官方)。 相反,Python 有一个 enum.Flags 类,它是 enum.Enum 类的子类。 但不要太担心,这里有好消息…

从旧枚举进行平滑过渡

将所有枚举代码突然更改为使用新语法是繁琐且容易出错的,因为这种必要的更改不容易找到。因此开发了一个forgiveness mode模式:

forgiveness mode 允许你继续使用旧的构造,但会默默地将它们转换为新的构造。例如,如果你写

flags = Qt.Alignment()
enum = Qt.AlignLeft

item.setForeground(QColor(Qt.green))

flags_type = QPainter.RenderHints
flags = QPainter.RenderHints()

chart_view.setRenderHint(QPainter.Antialiasing)

实际上,你得到的是一个模仿以下代码的结构,这是编写Flags和Enums的推荐方式:

flags = Qt.AlignmentFlag(0)
enum = Qt.AlignmentFlag.AlignLeft

item.setForeground(QColor(Qt.GlobalColor.green))

flags_type = QPainter.RenderHint
flags = QPainter.RenderHint(0)

chart_view.setRenderHint(QPainter.RenderHint.Antialiasing)

这样做的效果是,只要新的枚举是类的属性,你最初可以忽略新旧枚举之间的差异。(这不适用于没有类的全局枚举,请参见下面的Limitations。)

宽恕模式和类型提示

当你检查例如 QtCore.pyi 时,你只会找到新的枚举,尽管旧的枚举仍然被允许。此外,行补全只会适用于新的结构,而不会建议旧的。

实现forgiveness mode这种方式的原因是

  • 为了使过渡尽可能顺利,但

  • 鼓励人们在编写新代码时使用新的枚举。

所以你可以继续写:

self.text.setAlignment(Qt.AlignCenter)

但这种结构被使用并推荐用于未来:

self.text.setAlignment(Qt.AlignmentFlag.AlignCenter)

限制

当枚举类嵌入到普通的PySide类中时,宽容模式工作得非常好。但有一些全局枚举,特别是QtMsgType是一个问题:

t = QtMsgType.QtDebugMsg

不能以快捷方式书写

t = QtDebugMsg

因为没有提供宽容模式实现的周围PySide类。通常,所需的更改很容易找到,因为它们经常出现在导入语句中。

权限API

跨平台权限API在Qt 6.5版本中引入,目前适用于macOS、iOS、Android和WebAssembly平台。通过此API,您的Qt应用程序可以检查和请求某些功能的权限,如摄像头、麦克风、位置、蓝牙、联系人和日历。更多关于权限API的信息可以在此博客文章中阅读。

当使用权限API的PySide6应用程序以解释模式运行时,即python .py,实现权限API的代码将无法工作。使您的PySide6应用程序使用权限API工作的唯一方法是打包应用程序。对于Android,这意味着使用pyside6-android-deploy: the Android deployment tool for Qt for Python工具,对于macOS,这意味着使用pyside6-deploy: the deployment tool for Qt for Python工具。

在解释模式下运行时,您可以使用以下if条件跳过权限检查/请求

is_deployed = "__compiled__" in globals()
if not is_deployed and sys.platform == "darwin":
    # code implementing permission check and request

这也可以在 PySide6 的 Camera 示例 中看到。* __compiled__ * 是一个 Nuitka 属性,用于检查应用程序是作为独立应用程序运行还是在 Python 的解释模式下运行。

安卓

对于Android,pyside6-android-deploy: the Android deployment tool for Qt for Python 负责识别应用程序所需的必要权限,并使用 元素将这些权限添加到 AndroidManifest.xml 中。

macOS

由于Android平台并未自动捆绑Python解释器,因此很明显,要使PySide6应用程序在Android上运行,您必须打包PySide6应用程序。这与macOS等桌面平台不同,在这些平台上,Python解释器及其包可以轻松安装和运行。

macOS 的问题在于,为了使权限 API 正常工作,您需要一个包含 Info.plist 文件的 macOS 包,该文件使用 usage description 字符串列出所有所需的权限。当 Python 以解释模式运行时,即当您运行 Python 时,Qt 权限 API 默认从 Python 解释器中获取 Info.plist,该文件不包含所需权限的 usage description 字符串。您当然可以修改 Python 框架安装的 Info.plist,以使 Qt 权限 API 在从终端运行 PySide6 应用程序时正常工作。然而,这不推荐。因此,唯一可行的解决方案是使用 pyside6-deploy: the deployment tool for Qt for Python 将 PySide6 应用程序打包为 macOS 应用程序包。这个 macOS 应用程序包将拥有自己的 Info.plist 文件。