将C++应用程序移植到Python

Qt for Python 允许你在 Python 应用程序中使用 Qt API。 那么接下来的问题是:移植现有的 C++ 应用程序需要什么?尝试将一个 Qt C++ 应用程序移植到 Python 以理解这一点。

在开始之前,请确保满足Qt for Python的所有先决条件。有关更多信息,请参阅Getting Started。此外,熟悉C++和Python中Qt的基本区别。

基本差异

本节重点介绍C++和Python之间的一些基本差异,以及Qt在这两种环境中的不同之处。

C++ 对比 Python

  • 为了代码的重用,C++和Python都提供了让一个代码文件使用另一个代码文件提供的功能的方式。在C++中,这是通过使用#include指令来访问重用代码的API定义来实现的。Python中的等效方式是import语句。

  • C++类的构造函数与其类名相同,并在运行之前自动调用任何基类的构造函数(按预定义的顺序)。在Python中,__init__()方法是类的构造函数,它可以显式地以任何顺序调用基类的构造函数。

  • C++ 使用关键字 this 来隐式引用当前对象。在 Python 中,你需要显式地将当前对象作为类的每个实例方法的第一个参数;通常命名为 self

  • 更重要的是,忘记花括号 {} 和分号 ;。

  • 在变量定义前加上global关键字,仅当它们需要全局作用域时。

var = None
def func(key, value = None):
  """Does stuff with a key and an optional value.

  If value is omitted or None, the value from func()'s
  last call is reused.
  """
  global var
  if value is None:
      if var is None:
          raise ValueError("Must pass a value on first call", key, value)
      value = var
  else:
      var = value
  doStuff(key, value)

在这个例子中,func() 会将 var 视为局部名称,而没有 global 语句。这会导致在 value is None 处理中访问 var 时出现 NameError。有关此问题的更多信息,请参阅 Python 参考文档

提示

Python 作为一种解释型语言,通常最简单的方法是在解释器中尝试你的想法。你可以在解释器中调用 help() 函数来查看任何内置函数或关键字的帮助信息。例如,调用 help(import) 应该会提供关于 import 语句的文档。

最后但同样重要的是,尝试一些例子来熟悉Python编码风格,并遵循PEP8 - 风格指南中概述的指导原则。

import sys

from PySide6.QtWidgets import QApplication, QLabel

app = QApplication(sys.argv)
label = QLabel("Hello World")
label.show()
sys.exit(app.exec())

注意

Qt 提供了用于管理应用程序特定需求的类,具体取决于应用程序是仅控制台(QCoreApplication)、带有 QtWidgets 的 GUI(QApplication),还是不带有 QtWidgets 的 GUI(QGuiApplication)。这些类加载必要的插件,例如应用程序所需的 GUI 库。在这种情况下,首先初始化的是 QApplication,因为应用程序具有带有 QtWidgets 的 GUI。

C++ 和 Python 环境中的 Qt

无论Qt是在C++还是Python应用程序中使用,其行为都是相同的。考虑到C++和Python使用不同的语言语义,两种Qt变体之间的一些差异是不可避免的。以下是一些你必须了解的重要差异:

  • Qt 属性: Q_PROPERTY 宏在 C++ 中用于添加一个带有 getter 和 setter 函数的公共成员变量。Python 中的替代方案是在 getter 和 setter 函数定义前使用 @property 装饰器。

  • Qt 信号与槽: Qt 提供了一种独特的回调机制,其中信号被发出以通知事件的发生,以便连接到该信号的槽可以对其作出反应。在 C++ 中,类定义必须在 public Q_SLOTS: 下定义槽,并在 Q_SIGNALS: 访问说明符下定义信号。您可以使用 QObject::connect() 函数的几种变体之一来连接这两者。Python 中的等效方法是函数定义前的 @Slot` 装饰器。这是为了将槽注册到 QtMetaObject 所必需的。

  • QString, QVariant, 和其他类型

    • Qt for Python 不提供对 QString 和 QVariant 的访问。你必须使用 Python 的原生类型来代替。

    • QChar 和 QStringRef 被表示为 Python 字符串,而 QStringList 被转换为字符串列表。

    • QDate, QDateTime, QTime, 和 QUrl 的 __hash__() 方法返回一个字符串表示,以便相同的日期(以及相同的日期/时间或时间或 URL)具有相同的哈希值。

    • QTextStream 的 bin(), hex(), 和 oct() 函数分别被重命名为 bin_(), hex_(), 和 oct_()。这应该可以避免与 Python 内置函数的名称冲突。

  • QByteArray: QByteArray 被视为没有编码的字节列表。Python 3 使用“bytes”。QString 表示为编码的人类可读字符串,这意味着它是一个“str”。

这是改进版的 Hello World 示例,展示了其中一些差异:

 1from __future__ import annotations
 2
 3import sys
 4import random
 5
 6from PySide6.QtWidgets import (QApplication, QLabel,
 7     QPushButton, QVBoxLayout, QWidget)
 8from PySide6.QtCore import Qt, Slot
 9
10class MyWidget(QWidget):
11    def __init__(self):
12        super().__init__()
13
14        self.hello = ["Hallo Welt", "Hei maailma", "Hola Mundo", "Привет мир"]
15
16        self.button = QPushButton("Click me!")
17        self.text = QLabel("Hello World")
18        self.text.setAlignment(Qt.AlignCenter)
19
20        self.layout = QVBoxLayout()
21        self.layout.addWidget(self.text)
22        self.layout.addWidget(self.button)
23        self.setLayout(self.layout)
24
25        self.button.clicked.connect(self.magic)
26
27    @Slot()
28    def magic(self):
29        self.text.setText(random.choice(self.hello))
30
31if __name__ == "__main__":
32    app = QApplication(sys.argv)
33
34    widget = MyWidget()
35    widget.resize(800, 600)
36    widget.show()
37
38    sys.exit(app.exec())

注意

在开发Python应用程序时,if块只是一个良好的实践。它让Python文件根据它是作为模块导入到另一个文件中还是直接运行而表现不同。__name__变量在这两种情况下会有不同的值。当文件直接运行时,它是__main__,而当作为模块导入时,它是模块的文件名(在这种情况下是hello_world_ex)。在后一种情况下,模块中定义的除了if块之外的所有内容都可以被导入文件使用。

请注意,QPushButton的clicked信号连接到magic函数,以随机更改QLabel的text属性。@Slot装饰器标记了作为槽的方法,并通知QtMetaObject关于它们的信息。

移植一个Qt C++示例

Qt 提供了几个 C++ 示例来展示其功能并帮助初学者学习。你可以尝试将这些 C++ 示例中的一个移植到 Python 中。books SQL 示例是一个很好的起点,因为它不需要你在 Python 中编写特定于 UI 的代码,而是可以使用其 .ui 文件。

以下章节将指导您完成移植过程: