Qt C++程序嵌入python解释器的代码摘录

4 篇文章 0 订阅
1 篇文章 0 订阅

我直接在Qt的例子程序textedit里面增加代码。

CPython C API的文档非常晦涩,而且没有好的教程。API的解释不容易看懂,也没有例子辅助理解。看了很多文章,终于成功嵌入python解释器,并增加自定义模块。

本例主要使用PyCXX这个辅助库来编写,感觉比较方便。具体和boost.python,PyBind11谁更好,我不清楚,但是boost的模板有时让人恐惧,出了问题会很麻烦。本例也主要参考了PyCXX的示例来写的,FreeCAD就是使用PyCXX嵌入python解释器的。

由于TextEdit类并没有什么值得暴露的方法和属性,我选择了向python添加QTextEdit的append方法。

由于windows下CPython使用特定版本的Visual Studio,所以写扩展模块是必须选择与CPython编译时同样的Visual Studio 版本。

CPython 与微软 Visual Studio 各版本对应关系

注意这个表格并不权威,有时同一版本不同时期发布的python解释器使用了不同版本的Visual Studio。

Python微软Visual Studio
v3.8v2017MSC v.1912 64 bit (AMD64)
v3.7v2017MSC v.1912 64 bit (AMD64)
v3.6v2015MSC v.1900 64 bit (AMD64)
v3.5v2015MSC v.1900 64 bit (AMD64)
v3.4v2010MSC v.1600 64 bit (AMD64)
v2.7v2008MSC v.1500 64 bit (AMD64)

main.cpp

#include "textedit.h"

#include <QApplication>
...
// 这是我增加的python类型
#include "PyTextEdit.h"
// 这是我增加的python模块
#include "AppPythonModule.h"

int main(int argc, char *argv[])
{
    Q_INIT_RESOURCE(textedit);

    QApplication a(argc, argv);
...

    TextEdit mw;
...

	// 目录应该放在配置文件,如果不设置PYTHONHOME,Py_Initialize会引起程序崩溃。
	Py_SetPythonHome(L"C:\\Python37");
	Py_Initialize();
	// 增加自定义模块App
	PyObject* modules = PyImport_GetModuleDict();
    PyObject* pAppModule = PyInit_App();
    PyDict_SetItemString(modules, "App", pAppModule);

    mw.show();
    int res = a.exec();

	Py_Finalize();

	return res;
}

PyTextEdit.h

#pragma once
#pragma push_macro("slots")
#undef slots
#include "CXX/Objects.hxx"
#include "CXX/Extensions.hxx"
#pragma pop_macro("slots")

#include <qglobal.h>
QT_BEGIN_NAMESPACE
class QTextEdit;
QT_END_NAMESPACE

void setTextEdit(QTextEdit *textEdit);

class PyTextEdit : public Py::PythonClass<PyTextEdit>
{
public:
	PyTextEdit(Py::PythonClassInstance *self, Py::Tuple &args, Py::Dict &kwds);
	~PyTextEdit();

	static void init_type(void);
	Py::Object getattro(const Py::String &name_) override;
	int PyTextEdit::setattro(const Py::String &name_, const Py::Object &value) override;

	Py::Object append(const Py::Tuple &args);
    PYCXX_VARARGS_METHOD_DECL( PyTextEdit, append )

private:
	Py::String m_value;
};

PyTextEdit.cpp

#include "PyTextEdit.h"
#include <QTextEdit>


QTextEdit *g_textEdit = nullptr;

void setTextEdit(QTextEdit *textEdit)
{
	g_textEdit = textEdit;
}


PyTextEdit::PyTextEdit(Py::PythonClassInstance *self, Py::Tuple &args, Py::Dict &kwds)
	: Py::PythonClass< PyTextEdit >( self, args, kwds )
{
}


PyTextEdit::~PyTextEdit()
{
}

void PyTextEdit::init_type(void)
{
    behaviors().name( "PyTextEdit" );
    behaviors().doc( "documentation for PyTextEdit class" );
    behaviors().supportGetattro();
    behaviors().supportSetattro();

    PYCXX_ADD_VARARGS_METHOD( append, append, "docs for append" );

    // Call to make the type ready for use
    behaviors().readyType();
}

Py::Object PyTextEdit::getattro( const Py::String &name_ )
{
    std::string name( name_.as_std_string( "utf-8" ) );

    if( name == "value" )
    {
        return m_value;
    }
    else
    {
        return genericGetAttro( name_ );
    }
}

int PyTextEdit::setattro( const Py::String &name_, const Py::Object &value )
{
    std::string name( name_.as_std_string( "utf-8" ) );

    if( name == "value" )
    {
        m_value = value;
        return 0;
    }
    else
    {
        return genericSetAttro( name_, value );
    }
}

Py::Object PyTextEdit::append( const Py::Tuple &args )
{
	Py::String line = args[0];
	if (g_textEdit) {
		auto ustr = line.as_unicodestring();
		g_textEdit->append(QString::fromWCharArray(ustr.c_str()));
	}
    return Py::None();
}

AppPythonModule.h

#pragma once
#pragma push_macro("slots")
#undef slots
#include "CXX/Objects.hxx"
#include "CXX/Extensions.hxx"
#pragma pop_macro("slots")

class AppPythonModule : public Py::ExtensionModule<AppPythonModule>
{
public:
	AppPythonModule();
	~AppPythonModule();
private:
	Py::Object makeTextEdit(const Py::Tuple &args, const Py::Dict &kwds);
};

#define EXPORT_SYMBOL

extern "C" EXPORT_SYMBOL PyObject *PyInit_App();

// symbol required for the debug version
extern "C" EXPORT_SYMBOL PyObject *PyInit_App_d();

AppPythonModule.cpp

#include "AppPythonModule.h"
#include "PyTextEdit.h"


AppPythonModule::AppPythonModule()
: Py::ExtensionModule<AppPythonModule>( "App" ) // this must be name of the file on disk e.g. app.so or app.pyd
{
    PyTextEdit::init_type();
	auto type = *PyTextEdit::type();

    // after initialize the moduleDictionary will exist
    initialize( "documentation for the App module" );
	
	// 添加自定义类型TextEdit
	PyModule_AddObject(this->module().ptr(), "TextEdit", (PyObject *)type);

    Py::Dict d( moduleDictionary() );
	Py::TupleN args;
	Py::Dict kwds;
	// 添加模块属性textEdit
    d["textEdit"] = makeTextEdit(args, kwds);
}

AppPythonModule::~AppPythonModule()
{
}

Py::Object AppPythonModule::makeTextEdit( const Py::Tuple &args, const Py::Dict &kwds )
{
    std::cout << "makeTextEdit Called with " << args.length() << " normal arguments." << std::endl;
    Py::List names( kwds.keys() );
    std::cout << "and with " << names.length() << " keyword arguments:" << std::endl;
    for( Py::List::size_type i=0; i< names.length(); i++ )
    {
        Py::String name( names[i] );
        std::cout << "    " << name << std::endl;
    }

    Py::Callable class_type( PyTextEdit::type() );

    Py::PythonClassObject<PyTextEdit> textEdit( class_type.apply( args, kwds ) );

    return textEdit;
}

static AppPythonModule *app;

extern "C" EXPORT_SYMBOL PyObject *PyInit_App()
{
#if defined(PY_WIN32_DELAYLOAD_PYTHON_DLL)
    Py::InitialisePythonIndirectPy::Interface();
#endif

    app = new AppPythonModule;
    return app->module().ptr();
}

// symbol required for the debug version
extern "C" EXPORT_SYMBOL PyObject *PyInit_App_d()
{
    return PyInit_App();
}

下面是我点击按钮时运行的测试python代码:

void TextEdit::runPython()
{
	// 必须使用utf8编码,否则报告下面错误。
	// SyntaxError: (unicode error) 'utf-8' codec can't decode byte 0xd5 in position 0: invalid continuation
	PyRun_SimpleString(u8R"raw(
import App
App.textEdit.value = "hello"
App.textEdit.append("这是python解释器添加的文本。\n");
textEdit2 = App.TextEdit()
textEdit2.append("这是python解释器通过自定义类型添加的文本。\n");
)raw");
}

结果图展示:
结果图

线程问题还没考虑。暂时就在主线程上跑的python解释器。如果python代码太耗时,应该会导致界面冻结。如果在其他线程跑,需要使用Qt的signal机制和主线程通信更新界面。
另外在多个线程跑python代码的同步问题也没有在这个例子中演示。

PyCXX类简介

  • CXX\Python3\Objects.hxx

Py::Object是PyObject*的包装类。它的派生类(Boolean, Bytes, Dict, Float, List, Long, String, Tuple, Type)包装了Python的固有类型。TupleN是Tuple的派生类,增加了构造Tuple的便利构造函数。

  • CXX\Python3\ExtensionType.hxx

PyClass这个类基本就是实现自定义类型的PyTypeObject对象,它又是通过PythonType来实现的。

template<TEMPLATE_TYPENAME T>
class PythonClass : public PythonExtensionBase
{
protected:
    explicit PythonClass( PythonClassInstance *self, Tuple &/*args*/, Dict &/*kwds*/ )
    : PythonExtensionBase()
    , m_class_instance( self )
    {
    }
    ...
    static ExtensionClassMethodsTable &methodTable()
    {
        static ExtensionClassMethodsTable *method_table;
        if( method_table == NULL )
            method_table = new ExtensionClassMethodsTable;
        return *method_table;
    }

    static void add_method( const char *name, PyCFunction function, int flags, const char *doc=NULL )
    {
        behaviors().set_methods( methodTable().add_method( name, function, flags, doc ) );
    }

    static PythonType &behaviors()
    {
        static PythonType *p;
        if( p == NULL )
        {
#if defined( _CPPRTTI ) || defined( __GNUG__ )
            const char *default_name = (typeid( T )).name();
#else
            const char *default_name = "unknown";
#endif
            p = new PythonType( sizeof( PythonClassInstance ), 0, default_name );
            p->set_tp_new( extension_object_new );
            p->set_tp_init( extension_object_init );
            p->set_tp_dealloc( extension_object_deallocator );

            // we are a class
            p->supportClass();

            // always support get and set attr
            p->supportGetattro();
            p->supportSetattro();
        }

        return *p;
    }
    
public:
    static PyTypeObject *type_object()
    {
        return behaviors().type_object();
    }

    static Object type()
    {
        return Object( reinterpret_cast<PyObject *>( behaviors().type_object() ) );
    }
    ...
    virtual Object self()
    {
        return Object( reinterpret_cast<PyObject *>( m_class_instance ) );
    }
private:
    PythonClassInstance *m_class_instance;
    };

PyClassInstance这个类是你自定义类型的Python对象。它很简单,而且不是类模板。

    struct PythonClassInstance
    {
        PyObject_HEAD
        PythonExtensionBase *m_pycxx_object;
    };

PyClassObject是一个简单的包装类,包装了PyClassInstance对象。就是Py::Long包装了Python的整数PyLongObject一样。
你要做的事就是从PythonClass派生实现自定义python类型,填充PyTypeObject的事情就交给PythonClass。

class PyTextEdit : public Py::PythonClass<PyTextEdit>

添加自定义类型TextEdit

PyModule_AddObject(this->module().ptr(), "TextEdit", (PyObject *)type);
  • PythonType.hxx

PythonType这个类是为了操作PyTypeObject。
PYCXX_VARARGS_METHOD_DECL宏把实例方法包装成静态方法,因为CPython最终调用的都是这样的静态方法。

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值