Python中调用C/C++
上一篇中
C++和Python相互调用(1)-CSDN博客,我们介绍了C++中调用Python的一些方法,这篇我们总结一下Python调用C++的一些方法
1、Python的C/C++拓展
使用 Python 的 C/C++ 扩展可以将 C++ 函数包装成 Python 函数,从而在 Python 中调用这些 C++ 函数。通过这种方式,可以利用 C++ 的性能优势,同时保持 Python 的易用性。以下是一个基本的步骤和示例,展示如何使用 Python 的 C 扩展机制将 C++ 函数包装成 Python 函数。
步骤概述
- 编写 C++ 代码:编写你想要包装的 C++ 函数。
- 创建包装代码:利用 Python C API 将 C++ 函数包装为 Python 函数。
- 编写 setup.py:使用 setuptools 编译和安装扩展模块。
- 编译扩展模块:运行 setup.py 进行编译。
- 在 Python 中使用:导入并使用扩展模块。
我们先来看一个简单的C++函数封装过程
编写 C++ 代码,
创建一个名为 example.cpp 的文件:
#include <Python.h>
// 一个简单的 C++ 函数
int add(int i, int j) {
return i + j;
}
// 包装函数,用于将 C++ 函数暴露给 Python
static PyObject* py_add(PyObject* self, PyObject* args) {
int i, j;
// 解析输入的 Python 参数
if (!PyArg_ParseTuple(args, "ii", &i, &j)) {
return NULL;
}
// 调用 C++ 函数并返回结果
int result = add(i, j);
return Py_BuildValue("i", result);
}
// 定义模块的方法表
static PyMethodDef ExampleMethods[] = {
{"add", py_add, METH_VARARGS, "Add two integers"},
{NULL, NULL, 0, NULL}
};
// 定义模块
static struct PyModuleDef examplemodule = {
PyModuleDef_HEAD_INIT,
"example",
NULL,
-1,
ExampleMethods
};
// 初始化模块
PyMODINIT_FUNC PyInit_example(void) {
return PyModule_Create(&examplemodule);
}
详细说明
- PyMethodDef 结构体定义了模块中的方法列表,每个方法包括一个 C 函数指针、方法名称、调用方式(如 METH_VARARGS)、和方法文档。
- PyModuleDef 结构体定义了模块的基本信息。
- PyMODINIT_FUNC 宏定义了模块的初始化函数,这个函数的名称必须是 PyInit_<module_name>。
编写 setup.py
创建一个名为 setup.py 的文件,用于编译和安装扩展模块:
from setuptools import setup, Extension
module = Extension('example', sources=['example.cpp'])
setup(
name='example',
version='1.0',
description='Python Package with C++ Extension',
ext_modules=[module],
)
编译扩展模块
在终端中运行以下命令来编译和安装模块:
python setup.py build
python setup.py install
在 Python 中使用
编译成功后,你可以在 Python 中导入并使用这个扩展模块:
import example
result = example.add(3, 4)
print(result) # 输出: 7
上面的例子只有一个简单的函数,我们来看一个复杂的一点的例子,C++类的包装
#include <Python.h>
// 定义一个简单的C++类
class MyData {
public:
int x;
double y;
};
// 将MyData类型暴露给Python
static PyObject* MyData_new(PyTypeObject* type, PyObject* args, PyObject* kwds) {
MyData* self = new MyData();
return reinterpret_cast<PyObject*>(self);
}
static void MyData_dealloc(MyData* self) {
delete self;
}
static PyMemberDef MyData_members[] = {
{"x", T_INT, offsetof(MyData, x), 0, "x value"},
{"y", T_DOUBLE, offsetof(MyData, y), 0, "y value"},
{NULL}
};
static PyTypeObject MyDataType = {
PyVarObject_HEAD_INIT(NULL, 0)
"mydata.MyData", /* tp_name */
sizeof(MyData), /* tp_basicsize */
0, /* tp_itemsize */
(destructor)MyData_dealloc, /* tp_dealloc */
0, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_reserved */
0, /* tp_repr */
0, /* tp_as_number */
0, /* tp_as_sequence */
0, /* tp_as_mapping */
0, /* tp_hash */
0, /* tp_call */
0, /* tp_str */
0, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT, /* tp_flags */
"MyData objects", /* tp_doc */
0, /* tp_traverse */
0, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
0, /* tp_methods */
MyData_members, /* tp_members */
0, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
0, /* tp_init */
0, /* tp_alloc */
MyData_new, /* tp_new */
};
static struct PyModuleDef mydata_module = {
PyModuleDef_HEAD_INIT,
"mydata", /* m_name */
"MyData extension", /* m_doc */
-1, /* m_size */
};
PyMODINIT_FUNC PyInit_mydata(void) {
PyObject* m;
MyDataType.tp_new = PyType_GenericNew;
if (PyType_Ready(&MyDataType) < 0)
return NULL;
m = PyModule_Create(&mydata_module);
if (m == NULL)
return NULL;
Py_INCREF(&MyDataType);
PyModule_AddObject(m, "MyData", (PyObject*)&MyDataType);
return m;
}
int main() {
Py_Initialize();
PyInit_mydata();
// 执行Python代码来操作MyData对象
PyRun_SimpleString("import mydata\n"
"d = mydata.MyData()\n"
"d.x = 42\n"
"d.y = 3.14\n"
"print(d.x, d.y)\n");
Py_Finalize();
return 0;
}
setuptools 是一个常用且方便的工具来编译和安装 Python 扩展模块,但并不是唯一的选择。你可以使用多种方法来编译 C++ 并生成 Python 扩展模块。以下是一些常见的替代方法:
使用 distutils
distutils 是 Python 标准库的一部分,也是早期用于编译和安装 Python 扩展模块的工具。它的使用方式与 setuptools 类似,但功能较少。
示例 setup.py 文件:
from distutils.core import setup, Extension
module = Extension('example', sources=['example.cpp'])
setup(
name='example',
version='1.0',
description='Python Package with C++ Extension',
ext_modules=[module],
)
编译和安装:
python setup.py build
python setup.py install
使用 cmake 和 scikit-build
scikit-build 是 setuptools 的一个扩展,允许使用 CMake 来配置和生成构建文件。CMake 是一个跨平台的构建系统,可以更好地处理复杂的编译过程和依赖管理。
安装 scikit-build:
pip install scikit-build
创建 CMakeLists.txt:
cmake_minimum_required(VERSION 3.5)
project(example)
find_package(PythonLibs REQUIRED)
include_directories(${PYTHON_INCLUDE_DIRS})
add_library(example MODULE example.cpp)
set_target_properties(example PROPERTIES PREFIX "")
创建 setup.py:
from skbuild import setup
from setuptools import find_packages
setup(
name='example',
version='1.0',
description='Python Package with C++ Extension',
packages=find_packages(),
)
编译和安装:
python setup.py build
python setup.py install
2、使用CTypes调用C/C++共享库
你可以手动使用编译器(如 g++ 或 clang++)编译 C++ 代码,并生成共享库,然后在 Python 中通过 ctypes 或 cffi 调用。
编译 C++ 文件为共享库:
// 一个简单的 C++ 函数
extern "C" __declspec(dllexport) int add(int i, int j) {
return i + j;
}
在 Python 中使用 ctypes:
import ctypes
mydll = ctypes.cdll.LoadLibrary('./example_dll.dll')
result = mydll.add(3, 4)
print(result) # 输出: 7
3、使用 pybind11
Pybind11 是一个使用广泛的库,用于在 C++ 和 Python 之间创建无缝的绑定。它允许你在 C++ 代码中轻松地调用 Python 函数,反之亦然。Pybind11 的设计目标是提供一个更紧密、更简洁的接口,使得 C++ 和 Python 之间的交互变得更简单和高效。
pip install pybind11
创建 example.cpp:
#include <pybind11/pybind11.h>
int add(int i, int j) {
return i + j;
}
PYBIND11_MODULE(example, m) {
m.def("add", &add, "A function that adds two numbers");
}
创建 setup.py:
from setuptools import setup, Extension
import pybind11
module = Extension(
'example',
sources=['example.cpp'],
include_dirs=[pybind11.get_include()]
)
setup(
name='example',
version='1.0',
description='Python Package with C++ Extension',
ext_modules=[module],
)
编译和安装:
python setup.py build
python setup.py install
Pybind11 的工作原理可以分为以下几个关键点:
- 头文件库:Pybind11 是一个头文件库,这意味着你只需要包含相应的头文件就可以使用它的功能,而不需要额外的编译或链接过程。
- 模板元编程:Pybind11 大量使用了 C++ 的模板元编程技术。这使得它可以自动推断类型,并生成适当的转换代码。这样,C++ 和 Python 之间的类型转换和函数调用变得非常高效。
- Python C API:Pybind11 在底层使用了 Python C API。这意味着它直接与 Python 解释器进行交互,从而实现低级别的操作,如对象的创建、属性的获取和设置、函数的调用等。
- 自动类型转换:Pybind11 能够自动在 C++ 和 Python 类型之间进行转换。例如,它可以将 C++ 的 std::string 自动转换为 Python 的 str,将 C++ 的 std::vector 转换为 Python 的 list,等等。这极大地简化了跨语言调用的复杂性。
- 模块和类绑定:Pybind11 提供了一种直观的方式来定义 Python 模块和类,并将其绑定到 C++ 实现。例如,你可以使用 py::module 定义一个模块,使用 py::class_ 定义一个类,并为其添加方法和属性。