当前位置:   article > 正文

用C/C++扩展Python,包括类、方法、变量(Windows环境)_c++实现python扩展

c++实现python扩展

环境和工具:Windows10、Visual Studio 2019、Python3.10、PowerShell、pip

内容:分别展示了使用Python/C API和pybind11,在Python中调用C++的类、方法、变量的过程。

注意:过程就是把C++文件封装成Python包在Python中导入包并使用。也就是说,该方法不支持C++类、方法、变量的动态修改,每次对包进行修改,都需要重新编译C++代码。

阅前提醒:本文不包含对于代码写法的解释,只提供了能跑通的代码、使用步骤。如果对代码写法感兴趣,建议搜索词:“Python 扩展 C++”。

目录

一、使用Python/C API

1.1 配置环境

1.2 被打包的代码

1.3 打包工具代码-C++ 

1.4 打包工具代码-setup.py

1.5 打包过程

1.6 使用包

二、使用pybind11

2.1 配置环境

2.2  CMakeLists

2.3 C++代码

2.4 Python代码


一、使用Python/C API

1.1 配置环境

在Visual Studio中新建空项目BindingTest。

配置CPython环境。配置方法见博客:C++使用Python/C API_Eliza_Her的博客-CSDN博客

1.2 被打包的代码

编写myclass.cpp,这里是要被打包的代码,包括下面三个内容:

  1. MyClass类
  2. my_variable变量
  3. update_variable方法
  1. #include <iostream>
  2. #include <string>
  3. #include <Python.h>
  4. // C++代码
  5. class MyClass {
  6. public:
  7. void printMessage(const std::string& message) {
  8. std::cout << "Message: " << message << std::endl;
  9. }
  10. };
  11. int my_variable = 42;
  12. void update_variable(int new_value) {
  13. my_variable = new_value;
  14. }

1.3 打包工具代码-C++ 

编写myClassPy.cpp,这是打包的工具代码,该代码为包定义了四个方法:

  1. 实例化MyClass类
  2. 调用MyClass的printMessage方法
  3. 读取my_variable
  4. 更新my_variable(注意,这个更新只在Python环境下有效,不能对C++环境中的my_variable造成影响)
  1. #include <Python.h>
  2. #include "myclass.cpp"
  3. #include "valueTest.cpp"
  4. static PyObject* create_instance(PyObject* self, PyObject* args) {
  5. MyClass* myclass = new MyClass();
  6. return PyCapsule_New(myclass, "myclass", NULL);
  7. }
  8. static PyObject* print_message(PyObject* self, PyObject* args) {
  9. PyObject* capsule;
  10. const char* message;
  11. if (!PyArg_ParseTuple(args, "Os", &capsule, &message)) {
  12. return nullptr;
  13. }
  14. const char* name = PyCapsule_GetName(capsule);
  15. if (name == nullptr) {
  16. return nullptr;
  17. }
  18. MyClass* instance = (MyClass*)PyCapsule_GetPointer(capsule, name);
  19. if (instance == nullptr) {
  20. return nullptr;
  21. }
  22. instance->printMessage(message);
  23. Py_RETURN_NONE;
  24. }
  25. static PyObject* get_variable(PyObject* self, PyObject* args) {
  26. return PyLong_FromLong(my_variable);
  27. }
  28. static PyObject* set_variable(PyObject* self, PyObject* args) {
  29. int new_value;
  30. if (!PyArg_ParseTuple(args, "i", &new_value)) {
  31. return NULL;
  32. }
  33. update_variable(new_value);
  34. Py_RETURN_NONE;
  35. }
  36. static PyMethodDef module_methods[] = {
  37. {"create_instance", create_instance, METH_NOARGS, "Create an instance of MyClass."},
  38. {"print_message", print_message, METH_VARARGS, "Print a message using MyClass."},
  39. {"get_variable", get_variable, METH_NOARGS, "Get the value of the variable."},
  40. {"set_variable", set_variable, METH_VARARGS, "Set the value of the variable."},
  41. {NULL, NULL, 0, NULL}
  42. };
  43. static struct PyModuleDef module_definition = {
  44. PyModuleDef_HEAD_INIT,
  45. "myclass",
  46. "A module that exposes a C++ class.",
  47. -1,
  48. module_methods
  49. };
  50. PyMODINIT_FUNC PyInit_myclass(void) {
  51. return PyModule_Create(&module_definition);
  52. }

1.4 打包工具代码-setup.py

 新建setup.py,输入以下内容,其中version和author可省略:

  1. from distutils.core import setup,Extension
  2. MOD = 'myclass'
  3. setup(name=MOD,
  4. version = '1.0',
  5. author = 'Eliza',
  6. ext_modules=[Extension(MOD,sources=['myClassPy.cpp'])])

1.5 打包过程

在管理员模式下运行PowerShell,运行以下语句:

  1. python setup.py build
  2. python setup.py install

如果没有报错,则可以看到,一开始的代码已经打包成了Python包,包名是myclass。

1.6 使用包

在python文件中输入以下代码并运行,以测试包的功能:

  1. import myclass
  2. value = myclass.get_variable()
  3. print("first use value: %d" %value)
  4. myclass.set_variable(233)
  5. value = myclass.get_variable()
  6. print("second use value: %d" %value)
  7. instance = myclass.create_instance()
  8. myclass.print_message(instance, "hello")

输出:

first use value: 42

second use value: 233

Message: hello

二、使用pybind11

官方文档对于基础用法给出了详细的示例,比本文列出的更广泛:

First steps - pybind11 documentation

Object-oriented code - pybind11 documentation

2.1 配置环境

与Linux环境不同,Windows环境下的pybind11的环境配置方法见该文章:【pybind11入门】Windows下为Python创建C++扩展|安装、CMake编译、调用 - 知乎 (zhihu.com)

官方给出的配置方法:Build systems - pybind11 documentation 

我采用了CMake配置法Windows环境下,使用pybind11为Python扩展C++分为以下步骤(具体操作参考链接):

  1. 用pip安装pytest
    pip install pytest
  2.  从github上获取pybind11,并通过cmake进行编译(最后一步可能报错,最后一步不能运行也没关系,只要不是严重错误就可以忽视)
    1. git clone https://github.com/pybind/pybind11
    2. cd pybind11
    3. mkdir build
    4. cd build
    5. cmake ..
    6. cmake --build . --config Release --target check
  3. 把pybind11复制到项目文件夹下
  4. 编写C++代码(见2.3节
  5. 编写CMakeLists(见2.2节),通过cmake编译项目
    1. mkdir build
    2. cd build
    3. cmake ..
  6. 用VS打开 项目名.vcxproj 文件,点击生成→生成解决方案
  7. build/Debug/ 目录下(注意:不是 Debug/ 目录下)找到 项目名.xxx.pyd 文件,把文件名中间的 .xxx 删去,将文件复制到python代码相同目录下
  8. 编写python代码,通过 import 项目名 导入包(见2.4节
  9. 运行python代码

注意:修改C++代码之后,从第6步开始执行,以获取新的pyd文件 。

2.2 CMakeLists

新建项目pybindTest,在项目根目录下新建CMakeLists.txt,内容如下:

  1. cmake_minimum_required(VERSION 3.4)
  2. project(pybindTest LANGUAGES CXX)
  3. add_subdirectory(pybind11)
  4. pybind11_add_module(pybindTest my_module.cpp)

2.3 C++代码

my_module.cpp,涉及如下操作:

  1. 全局变量
  2. 函数(支持默认参数)
  3. 类(支持带参方法、修改属性【即动态参数】)
  1. #include <pybind11/pybind11.h>
  2. #include <iostream>
  3. #include <string>
  4. namespace py = pybind11;
  5. int my_variable = 42;
  6. int add(int x, int y) {
  7. return x + y;
  8. }
  9. class MyClass {
  10. public:
  11. MyClass(int x, int y) :m_x(x), m_y(y) {}
  12. void printMessage(const std::string& message) {
  13. std::cout << "Message: " << message << std::endl;
  14. }
  15. void printLocation() {
  16. std::cout << "Location: (" << m_x << ", " << m_y << ")" << std::endl;
  17. }
  18. std::string className = "Eliza";
  19. private:
  20. int m_x, m_y;
  21. };
  22. PYBIND11_MODULE(pybindTest, m) {
  23. m.doc() = "test pybind11";
  24. //定义变量
  25. m.attr("name") = "Eliza";
  26. m.attr("age") = 108;
  27. //临时定义函数内容
  28. m.def("get_variable", []() {
  29. return my_variable;
  30. }, "get variable");
  31. m.def("set_variable", [](int value) {
  32. my_variable = value;
  33. }, "set variable");
  34. //事先定义函数内容,通过py::arg设置参数的默认值
  35. //只有前两个参数必填,后3个参数可不填
  36. m.def("add", &add, "A function that adds two numbers",
  37. py::arg("i") = 1, py::arg("j") = 2);
  38. //类,及其带参方法init和printMessage、动态参数name
  39. py::class_<MyClass>(m, "MyClass", py::dynamic_attr())
  40. .def(py::init<int, int>())
  41. .def("printMessage", &MyClass::printMessage)
  42. .def("printLocation", &MyClass::printLocation)
  43. .def_readwrite("name", &MyClass::className);//另一种写法:def_property()
  44. }

2.4 Python代码

  1. import pybindTest
  2. print("name: ", pybindTest.name)
  3. print("age: ", pybindTest.age)
  4. value = pybindTest.get_variable()
  5. print("Variable value: ", value)
  6. pybindTest.set_variable(100)
  7. new_value = pybindTest.get_variable()
  8. print("New variable value: ", new_value)
  9. addNum = pybindTest.add()
  10. print("Default add result: ", addNum)
  11. addNum = pybindTest.add(3, 4)
  12. print("New add result: ", addNum)
  13. myclass = pybindTest.MyClass(1, 3)
  14. myclass.printLocation()
  15. print("myclass name: ", myclass.name)
  16. myclass.name = "Sera"
  17. print("new myclass name: ", myclass.name)
  18. myclass.printMessage("Hello")

输出:

name:  Eliza
age:  108
Variable value:  42
New variable value:  100
Default add result:  3
New add result:  7
Location: (1, 3)
myclass name:  Eliza
new myclass name:  Sera
Message: Hello

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Monodyee/article/detail/555682
推荐阅读
相关标签
  

闽ICP备14008679号