完美的二人组:在C++中轻松利用Python库

文摘   2024-11-01 09:00   广东  

点击上方【蓝字】关注博主

 C++ 以其高性能和对硬件的精细控制著称,而 Python 以简洁易用和丰富的库而闻名。当我们想要在 C++ 项目中引入 Python 的强大功能,例如数据科学、机器学习或图形处理,该如何实现呢?本文将深入探讨如何在 C++ 中轻松利用 Python 库,通过 Cython 和 Boost.Python 等工具,将 Python 的优势融入 C++ 项目,提升开发效率,拓展功能边界。

01

简介 

C++的优势和应用领域:

  • C++是一种高性能的编程语言,特别适用于需要快速执行和高效资源利用的应用程序,如游戏开发、系统编程和嵌入式设备。

  • C++提供了对硬件和内存的更直接的控制,使其成为编写底层系统、驱动程序和实时系统的首选语言。

  • 应用领域:用于底层系统编程、游戏开发、高性能计算和金融领域,以及对性能要求较高的应用。


Python的优势和应用领域:

  • Python是一种易于学习和使用的高级编程语言,适合初学者和快速开发原型。

  • Python具有丰富的第三方库和资源,使得快速开发和简洁的代码成为可能,并且往往代码行数较少。

  • Python在数据科学、机器学习和人工智能领域具有强大的生态系统和丰富的库支持。


C++中利用Python库的必要性和优势:

  1. Python拥有丰富的第三方库和资源,特别是在数据科学、机器学习、人工智能等领域。利用Python库可以为C++项目快速引入这些高级功能,提高开发效率。

  2. Python作为一种动态语言,在编写程序时更加灵活,而且它通常需要更少的代码行数。C++可以利用Python库更快地创建原型、测试和实现功能。

  3. Python在科学计算、数据分析、图形处理等领域有着丰富的资源和库。通过在C++中利用Python库,可以将这些领域的功能整合到C++项目中。

02

为什么在C++中使用Python库?

C++的优点:高性能、对内存和硬件有更直接的控制。

C++的缺点:

  1. 相对于Python等高级语言,C++的语法和概念较为复杂,学习曲线陡峭。

  2. 写代码相对繁琐。

  3. 容易出现内存泄漏和指针错误。


Python的优点:简单易学、生态丰富。

Python的缺点:

  1. Python是一种解释型语言,执行速度相对较慢。

  2. 内存占用较高。

C++中利用Python库的好处:

  1. Python拥有简洁清晰的语法和丰富的标准库以及第三方库,利用Python库可以为C++项目快速引入高级功能。通过Python的动态特性和丰富的库函数,能够更快地创建原型、测试和实现各种功能。

  2. Python在数据科学、机器学习、人工智能等领域拥有庞大的生态系统,包括NumPy、Pandas、SciPy、TensorFlow等众多优秀的库和工具。通过在C++中利用Python库,可以轻松地调用这些高级功能,从而为C++项目引入强大的数据处理、机器学习和人工智能能力。

  3. Python是科学计算和数据分析领域的主流语言,拥有丰富的科学计算库和工具。

C++中利用Python的一种常见的方式是通过Cython或Boost.Python将Python代码嵌入到C++项目中,以利用Python的丰富库函数来加速C++开发。

示例:使用Python的matplotlib库来绘制图表,而无需编写复杂的图形库调用。

#include <Python.h>

int main() {
Py_Initialize();

// 调用Python库中的matplotlib,绘制一幅简单的图形
PyRun_SimpleString("import matplotlib.pyplot as plt\n"
"import numpy as np\n"
"x = np.linspace(0, 10, 100)\n"
"y = np.sin(x)\n"
"plt.plot(x, y)\n"
"plt.show()");

Py_Finalize();
return 0;
}
03

封装Python库 

当在C++项目中嵌入Python代码时,Cython和Boost.Python都是常用的工具。

Cython:

  • 作用:Cython是一种使用Python语法和C/C++语言能力的编译器,可以将Python代码转换为C或C++代码,然后编译成扩展模块,以供调用。

  • 优势:Cython简化了C++和Python之间的集成过程,方便地利用Python的高级语法和C/C++的执行效率。它还允许在Python中调用C/C++代码,从而提供了更高的性能和可扩展性。

Boost.Python:

  • 作用:Boost.Python是一个C++库,旨在使C++类和函数可供Python调用。它为C++和Python之间的交互提供了高级的抽象层,简化了C++库的导出和Python代码的调用。

  • 优势:Boost.Python使得在C++项目中集成Python更加容易,同时提供了更多的控制和灵活性。它允许在C++中定义Python可调用的函数和类,并提供了丰富的API来处理Python对象和异常。

用Cython来封装Python库供C++使用:

  1. 要创建一个.pyx文件来编写Cython代码,封装要使用的Python库。

    # example.pyx

    cdef extern from "math.h":
    double sqrt(double x)

    def c_sqrt(double x):
    return sqrt(x)
  2. 接着,创建一个setup.py文件,用于构建Cython代码成为一个可被C++调用的扩展模块:

    # setup.py

    from distutils.core import setup
    from Cython.Build import cythonize

    setup(
    ext_modules=cythonize("example.pyx")
    )
  3. 使用以下命令来构建并使用Cython生成的扩展模块:

    $ python setup.py build_ext --inplace

    这样就可以在C++代码中调用c_sqrt函数来使用Python的math.sqrt函数。

使用Boost.Python来封装Python库供C++使用:

  1. 创建一个C++文件来定义要封装的Python库的接口,例如math.cpp:

    #include <boost/python.hpp>
    #include <cmath>

    double c_sqrt(double x) {
    return std::sqrt(x);
    }

    BOOST_PYTHON_MODULE(example) {
    using namespace boost::python;
    def("c_sqrt", c_sqrt);
    }
  2. 使用Boost.Python的工具来构建并导出C++扩展模块,使其可以调用:

    $ g++ -shared -o example.so -fPIC -I/usr/include/python3.7 math.cpp -lpython3.7

在使用Cython或Boost.Python封装Python库供C++使用时,需要注意内存管理和异常处理,以避免内存泄漏和异常导致的程序不稳定。

内存泄漏:

  1. 在C++中,可以使用智能指针来管理动态内存,比如std::shared_ptr或std::unique_ptr。

  2. 仔细检查Cython或Boost.Python生成的代码,确保在导出的C++接口中正确地释放内存。

异常处理:

  1. 在导出的C++接口中添加异常处理:在C++代码中,可以使用try-catch块来捕获Python引发的异常,然后进行合适的处理或转换为C++异常。

  2. 在Python代码中进行异常处理:在Python代码中,确保捕获和处理可能的异常,以避免向C++代码传递未处理的异常。


04

在C++中使用Python库 

(1)示例一:在C++中调用Cython封装Python的math库的动态链接库来使用Python的math库。 Cython封装的代码如下:

# example.pyx

cdef extern from "math.h":
double sqrt(double x)

def c_sqrt(double x):
return sqrt(x)

使用以下命令将此封装代码编译成一个动态链接库:

$ cythonize -i -3 example.pyx

生成一个名为example.cpython-36m-darwin.so的动态链接库。接下来,创建一个C++源文件main.cpp,其中包含对动态链接库中函数的调用:

// main.cpp

#include <iostream>
#include <Python.h>

int main() {
Py_Initialize(); // 初始化Python解释器
PyRun_SimpleString("import sys\nsys.path.append(\"/pylib\")"); // 添加Python库所在的路径
PyObject* pModule = PyImport_ImportModule("example"); // 导入封装的Python库
if (pModule) {
PyObject* pFunc = PyObject_GetAttrString(pModule, "c_sqrt"); // 获取Python函数对象
if (pFunc && PyCallable_Check(pFunc)) {
PyObject* pArgs = PyTuple_Pack(1, PyFloat_FromDouble(25.0)); // 准备函数参数
PyObject* pValue = PyObject_CallObject(pFunc, pArgs); // 调用Python函数
double result = PyFloat_AsDouble(pValue); // 获取函数返回值
std::cout << "Square root of 25: " << result << std::endl;
Py_DECREF(pArgs); // 释放参数对象
Py_DECREF(pValue); // 释放返回值对象
}
Py_DECREF(pFunc); // 释放函数对象
}
Py_DECREF(pModule); // 释放模块对象
Py_Finalize(); // 释放Python解释器
return 0;
}

使用以下命令编译C++源文件,并链接到Python的库:

$ g++ -o main main.cpp -I/pylib -L/pylib -lpython3.6m

(2)示例二:在C++中调用Python库的函数并获取返回值。

编写Python脚本example.py,其中定义了一个简单的函数add

# example.py
def add(a, b):
return a + b

创建一个C++源文件main.cpp,其中包含对Python库的调用代码:

// main.cpp

#include <Python.h>

int main() {
Py_Initialize(); // 初始化Python解释器

// 导入Python脚本
PyObject* pModule = PyImport_ImportModule("example");
if (pModule) {
// 获取Python函数对象
PyObject* pFunc = PyObject_GetAttrString(pModule, "add");
if (pFunc && PyCallable_Check(pFunc)) {
// 准备函数参数
PyObject* pArgs = PyTuple_Pack(2, PyLong_FromLong(3), PyLong_FromLong(4));
// 调用Python函数
PyObject* pValue = PyObject_CallObject(pFunc, pArgs);
// 获取函数返回值
long result = PyLong_AsLong(pValue);
// 输出返回值
printf("Result of add function: %ld\n", result);
// 释放参数对象
Py_DECREF(pArgs);
// 释放返回值对象
Py_DECREF(pValue);
}
// 释放函数对象
Py_XDECREF(pFunc);
// 释放模块对象
Py_DECREF(pModule);
}
// 释放Python解释器
Py_Finalize();

return 0;
}

使用以下命令编译C++源文件,并链接到Python的库:

g++ -o main main.cpp -I/pylib -L/pylib -lpython3.7

(3)示例三:C++数据结构与Python对象交互。

实现一个名为Point的简单的C++数据结构,其由x和y坐标组成:

// point.h

#ifndef POINT_H
#define POINT_H

struct Point {
double x;
double y;
};

#endif

包装Point结构,并将其导出为一个Python模块。

// main.cpp

#include <Python.h>
#include "point.h"

static PyObject* Point_create(PyObject* self, PyObject* args) {
double x, y;
if (!PyArg_ParseTuple(args, "dd", &x, &y)) {
return NULL;
}

Point* p = new Point{x, y};
return PyCapsule_New(p, "Point", NULL);
}

static PyObject* Point_getX(PyObject* self, PyObject* args) {
PyObject* obj;
if (!PyArg_ParseTuple(args, "O", &obj)) {
return NULL;
}

Point* p = static_cast<Point*>(PyCapsule_GetPointer(obj, "Point"));
return PyFloat_FromDouble(p->x);
}

static PyObject* Point_getY(PyObject* self, PyObject* args) {
PyObject* obj;
if (!PyArg_ParseTuple(args, "O", &obj)) {
return NULL;
}

Point* p = static_cast<Point*>(PyCapsule_GetPointer(obj, "Point"));
return PyFloat_FromDouble(p->y);
}

static PyMethodDef Point_methods[] = {
{"create", Point_create, METH_VARARGS, "Create a Point instance"},
{"getX", Point_getX, METH_VARARGS, "Get the x coordinate"},
{"getY", Point_getY, METH_VARARGS, "Get the y coordinate"},
{NULL, NULL, 0, NULL} // Sentinel
};

static struct PyModuleDef Point_module = {
PyModuleDef_HEAD_INIT,
"Point",
"A module that provides a Point data structure",
-1,
Point_methods
};

PyMODINIT_FUNC PyInit_Point() {
return PyModule_Create(&Point_module);
}

使用以下命令来编译C++代码并生成一个名为Point.so的动态链接库:

g++ -o point.o -c -I /pylib main.cpp
g++ -shared -o Point.so point.o -L /pylib -lpython3.7

在Python中导入Point模块,并使用create函数创建一个Point实例,并获取其x和y坐标:

# main.py

import Point

# 创建Point实例
point = Point.create(3.0, 4.0)

# 获取坐标值
x = Point.getX(point)
y = Point.getY(point)

print("x coordinate:", x)
print("y coordinate:", y)


05

完整的示例 

使用Python的科学计算库NumPy来进行数值计算,并使用Matplotlib来绘制图表。

// main.cpp
#include <Python.h>

int main() {
Py_Initialize();

// 导入NumPy库
PyObject* numpy = PyImport_ImportModule("numpy");

// 创建一维数组
PyObject* npArray = PyObject_CallMethod(numpy, "array", "[(iiii)]", 1, 2, 3, 4);

// 将数组传递给Matplotlib进行绘图
PyObject* pyplot = PyImport_ImportModule("matplotlib.pyplot");
PyObject* plotArgs = PyTuple_Pack(1, npArray);
PyObject_CallMethodObjArgs(pyplot, PyUnicode_FromString("plot"), plotArgs, NULL);
PyObject_CallMethod(pyplot, "show", NULL);

Py_Finalize();

return 0;
}

使用以下命令来编译C++源文件,并链接到Python的库:

g++ -o main main.cpp -I/pylib -L/pylib -lpython3.7

在执行生成的可执行文件后,应该会触发Matplotlib显示一个简单的图表,展示了创建的一维数组的值。


06

总结

  1. Python拥有丰富的开源库和框架,用于各种任务,包括科学计算、数据分析、人工智能等,这使得在C++项目中使用Python库可以快速获得成熟的解决方案。

  2. Python通常比C++更易于编写和阅读,因此使用Python库可以加快开发速度。

  3. 通过在C++项目中调用Python库,可以轻松地利用Python生态系统的各种功能而不必移植整个项目到Python。

公众号: Lion 莱恩呀

微信号: 关注获取

扫码关注 了解更多内容

点个 在看 你最好看




Lion 莱恩呀
专注分享高性能服务器后台开发技术知识,涵盖多个领域,包括C/C++、Linux、网络协议、设计模式、中间件、云原生、数据库、分布式架构等。目标是通过理论与代码实践的结合,让世界上看似难以掌握的技术变得易于理解与掌握。
 最新文章