C++和Python相互调用(2)

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_ 定义一个类,并为其添加方法和属性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值