使用PythonQt实现C++与Python混合编程

PythonQt提供了一种将python脚本语言嵌入到Qt C++程序中的简单方法。
我们可以使用一种脚本语言,扩展我们的应用,Qt中对于脚本化扩展应用程序有两种方法

很多知名的软件都是用python脚本的方式,扩展自己的应用程序,下面列举一下使用该方式扩展应用的知名软件:

  • Blender (开源三维动画制作软件)
  • Autodesk Maya (三维动画制作软件)
  • OpenOffice.org (跨平台的办公室软件套件)
  • MeVisLab (医学图像处理软件)
  • Scribus (桌面排版软件)

参考链接:
Embedding Python into Qt Applications
PythonQt主页
pythonqt源码下载

下面介绍关于PythonQt的简单使用


1. C++调用python脚本

关于pythonqt的编译这里就不过多的介绍了,简单的说分为三个步骤:

  1. 下载官方编译好的python库或者自己编译python的源代码。
  2. 下载pythonqt的源码。
  3. 修改pythonqt工程配置文件中的python库的路径,直接编译即可。

在使用pythonqt的时候,需要先对其进行初始化,使用头文件 PythonQt.h

PythonQt::init();

如果使用封装好的Qt类型,使用如下代码初始化,使用头文件 PythonQt_QtAll.h

PythonQt_QtAll::init();

使用函数 evalScript() , 执行python代码

// 获取 __main__ python module
PythonQtObjectPtr mainModule = PythonQt::self()->getMainModule();
QVariant result = mainModule.evalScript("100 + 200", Py_eval_input);
qDebug() << result;

这里使用函数 PythonQt::self()->getMainModule() 获取 __main__ 模块,然后调用函数 evalScript 执行python 脚本。

使用函数 evalFile() 执行一个python脚本

PythonQtObjectPtr mainModule = PythonQt::self()->getMainModule();
mainModule.evalFile(":example.py");

执行python的函数,并传递参数

mainModule.evalScript("def addFunc(num1, num2):\n   return num1 + num2");
QVariantList nums;
nums << 100 << 200;
result = mainModule.call("addFunc", nums);
qDebug() << "Called addFunc the result is " << result;

2. python中调用C++

2.1 直接使用Qt中的模块

如果编译了 PythonQt_QtAll 库,则可以直接使用Qt中提供的类,他提供了完整的QtAPI的python封装(包括C++类和QObject类的所有非槽函数、槽函数、信号、属性等等)。

包括如下模块 QtCore、QtGui、QtNetwork、QtOpenGL、QtSql、QtSvg、QtWebKit、QtXml、QtXmlPatterns、QtMultimedia、QtQml、QtQuick ,这些模块都是作为 PythonQt 的子模块使用。 对于 QtQuick 的支持还处于实验性阶段,目前不支持从python中注册新的QML组件。支持多态比如 QEvent 和多继承比如 QGraphicsTextItem

下面是一个实例
使用pythonQt制作如下的界面:

python中的代码如下:

from PythonQt import QtCore, QtGui

# 创建Widget和布局
mainWidget = QtGui.QWidget()
mainWidget.setWindowTitle('PythonWidget')
mainLayout = QtGui.QVBoxLayout(mainWidget)

# 创建TextEdit和一个按钮,并添加到布局
textEdit = QtGui.QTextEdit()
mainLayout.addWidget(textEdit)
testButton = QtGui.QPushButton('OK')
mainLayout.addWidget(testButton)

# 点击按钮时执行的函数
def onClicekedButton():
    textEdit.append('Hello PythonQt')

# 连接信号和处理函数
testButton.connect('clicked()', onClicekedButton)
# 显示界面
mainWidget.resize(800, 600)
mainWidget.show()

点击按钮时,文本添加字符串 Hello PythonQt


2.2 python中使用C++对象

下面是使用C++的一个类:

头文件

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

    Q_INVOKABLE void setTextEditText(const QString& text);

private:
    QTextEdit* m_pTextEdit = nullptr;
};

cpp文件

Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    QVBoxLayout* mainLayout = new QVBoxLayout(this);

    m_pTextEdit = new QTextEdit;
    m_pTextEdit->setObjectName("TestTextEdit");

    mainLayout->addWidget(m_pTextEdit);
}

Widget::~Widget()
{

}

void Widget::setTextEditText(const QString& text)
{
    m_pTextEdit->setText(text);
}

使用函数 addObject() 添加一个对象到python环境中:

调用如下:

Widget w;
w.show();
// 添加一个对象
mainModule.addObject("testWidget", &w);
// 添加终端
PythonQtScriptingConsole console(nullptr, mainModule);
console.show();

这里使用了 PythonQtScriptingConsole 类创建了一个python终端窗口。 可以直接在终端中调用添加的对象的函数,这里可以调用槽函数、使用 Q_INVOKABLE 修饰的函数、属性、信号等。

同时也可以通过objectname定位到子控件:


2.3 python中使用C++的类

PythonQt提供了一种通用的方法,来实现不论继承QObject的类还是普通的C++类来封装类提供给python环境。

如果想要在python中调用C++的类,只需如下三个步骤:

  1. 定义自定义类。
  2. 实现装饰器。
  3. 注册到Python环境中。

通过继承QObject实现包装器,并以特定的命名完成构造等函数的实现。

  • SomeClassName* new_SomeClassName(…) 定义构造函数
  • void delete_SomeClassName(SomeClassName* o) 定义析构函数
  • anything static_SomeClassName_someMethodName(…) 定义静态函数
  • anything someMethodName(SomeClassName* o, …) 定义普通函数,这里的第一参数同必须是该类型的指针。
注意:以上这些参数均要在槽函数中定义。

下面是一个普通的类定义:

// 测试普通类
class NormalClass
{
public:
    NormalClass(){
        qDebug() << __FUNCTION__;
    }
    ~NormalClass(){
        qDebug() << __FUNCTION__;
    }

    QString m_sTestName = "";
};

对于没有继承自 QObject 的类,还需要加上如下代码

// register it to the meta type system
Q_DECLARE_METATYPE(NormalClass)

下面是装饰器的定义

class NormalClassWrapper : public QObject
{
    Q_OBJECT

public slots:
    // Normal Class
    NormalClass* new_NormalClass(){ return new NormalClass; }
    void delete_NormalClass(NormalClass* o){ delete o; }

    void setTestName(NormalClass* o, const QString& name){ o->m_sTestName = name; }
    QString getTestName(NormalClass* o) { return o->m_sTestName; }

    // Widget
    Widget* new_Widget(QWidget* parent = nullptr){return new Widget(parent);}
    void delete_Widget(Widget* o){ delete o; }
};

这里的 Widget 是之前定义的那个 Widget

需要 注意 的是,如果是继承自 QObject 的对象, 使用函数 registerClass() 注册;如果是普通的C++类,则需要使用函数 registerCPPClass() 注册。 如果使用 registerCPPClass() 注册的继承自 QObject ,则不能使用默认的属性、普通函数、槽函数等等 QObject 特有的特性, 除非在装饰器中自己实现相关的定义。

下面是注册代码:

// 注册普通类
PythonQt::self()->registerCPPClass("NormalClass", "", \
    "example", PythonQtCreateObject<NormalClassWrapper>);
// 注册继承自QObject的类
PythonQt::self()->registerClass(&Widget::staticMetaObject, \
    "example", PythonQtCreateObject<NormalClassWrapper>);

测试python脚本

from PythonQt import *

normalObj = example.NormalClass()
normalObj.setTestName('不会飞的纸飞机')

w = example.Widget()
w.setTextEditText(normalObj.getTestName())
w.show()
不会飞的纸飞机
扫一扫二维码,了解我的更多动态。

下一篇文章:Qt趣味开发之打造一个3D名字渲染小工具