您现在的位置是:首页 >技术杂谈 >用C/C++扩展Python,包括类、方法、变量(Windows环境)网站首页技术杂谈
用C/C++扩展Python,包括类、方法、变量(Windows环境)
环境和工具: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 配置环境
在Visual Studio中新建空项目BindingTest。
配置CPython环境。配置方法见博客:C++使用Python/C API_Eliza_Her的博客-CSDN博客
1.2 被打包的代码
编写myclass.cpp,这里是要被打包的代码,包括下面三个内容:
- MyClass类
- my_variable变量
- update_variable方法
#include <iostream>
#include <string>
#include <Python.h>
// C++代码
class MyClass {
public:
void printMessage(const std::string& message) {
std::cout << "Message: " << message << std::endl;
}
};
int my_variable = 42;
void update_variable(int new_value) {
my_variable = new_value;
}
1.3 打包工具代码-C++
编写myClassPy.cpp,这是打包的工具代码,该代码为包定义了四个方法:
- 实例化MyClass类
- 调用MyClass的printMessage方法
- 读取my_variable
- 更新my_variable(注意,这个更新只在Python环境下有效,不能对C++环境中的my_variable造成影响)
#include <Python.h>
#include "myclass.cpp"
#include "valueTest.cpp"
static PyObject* create_instance(PyObject* self, PyObject* args) {
MyClass* myclass = new MyClass();
return PyCapsule_New(myclass, "myclass", NULL);
}
static PyObject* print_message(PyObject* self, PyObject* args) {
PyObject* capsule;
const char* message;
if (!PyArg_ParseTuple(args, "Os", &capsule, &message)) {
return nullptr;
}
const char* name = PyCapsule_GetName(capsule);
if (name == nullptr) {
return nullptr;
}
MyClass* instance = (MyClass*)PyCapsule_GetPointer(capsule, name);
if (instance == nullptr) {
return nullptr;
}
instance->printMessage(message);
Py_RETURN_NONE;
}
static PyObject* get_variable(PyObject* self, PyObject* args) {
return PyLong_FromLong(my_variable);
}
static PyObject* set_variable(PyObject* self, PyObject* args) {
int new_value;
if (!PyArg_ParseTuple(args, "i", &new_value)) {
return NULL;
}
update_variable(new_value);
Py_RETURN_NONE;
}
static PyMethodDef module_methods[] = {
{"create_instance", create_instance, METH_NOARGS, "Create an instance of MyClass."},
{"print_message", print_message, METH_VARARGS, "Print a message using MyClass."},
{"get_variable", get_variable, METH_NOARGS, "Get the value of the variable."},
{"set_variable", set_variable, METH_VARARGS, "Set the value of the variable."},
{NULL, NULL, 0, NULL}
};
static struct PyModuleDef module_definition = {
PyModuleDef_HEAD_INIT,
"myclass",
"A module that exposes a C++ class.",
-1,
module_methods
};
PyMODINIT_FUNC PyInit_myclass(void) {
return PyModule_Create(&module_definition);
}
1.4 打包工具代码-setup.py
新建setup.py,输入以下内容,其中version和author可省略:
from distutils.core import setup,Extension
MOD = 'myclass'
setup(name=MOD,
version = '1.0',
author = 'Eliza',
ext_modules=[Extension(MOD,sources=['myClassPy.cpp'])])
1.5 打包过程
在管理员模式下运行PowerShell,运行以下语句:
python setup.py build
python setup.py install
如果没有报错,则可以看到,一开始的代码已经打包成了Python包,包名是myclass。
1.6 使用包
在python文件中输入以下代码并运行,以测试包的功能:
import myclass
value = myclass.get_variable()
print("first use value: %d" %value)
myclass.set_variable(233)
value = myclass.get_variable()
print("second use value: %d" %value)
instance = myclass.create_instance()
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++分为以下步骤(具体操作参考链接):
- 用pip安装pytest
pip install pytest
- 从github上获取pybind11,并通过cmake进行编译(最后一步可能报错,最后一步不能运行也没关系,只要不是严重错误就可以忽视)
git clone https://github.com/pybind/pybind11 cd pybind11 mkdir build cd build cmake .. cmake --build . --config Release --target check
- 把pybind11复制到项目文件夹下
- 编写C++代码(见2.3节)
- 编写CMakeLists(见2.2节),通过cmake编译项目
mkdir build cd build cmake ..
- 用VS打开 项目名.vcxproj 文件,点击生成→生成解决方案
- 从 build/Debug/ 目录下(注意:不是 Debug/ 目录下)找到 项目名.xxx.pyd 文件,把文件名中间的 .xxx 删去,将文件复制到python代码相同目录下
- 编写python代码,通过 import 项目名 导入包(见2.4节)
- 运行python代码
注意:修改C++代码之后,从第6步开始执行,以获取新的pyd文件 。
2.2 CMakeLists
新建项目pybindTest,在项目根目录下新建CMakeLists.txt,内容如下:
cmake_minimum_required(VERSION 3.4)
project(pybindTest LANGUAGES CXX)
add_subdirectory(pybind11)
pybind11_add_module(pybindTest my_module.cpp)
2.3 C++代码
my_module.cpp,涉及如下操作:
- 全局变量
- 函数(支持默认参数)
- 类(支持带参方法、修改属性【即动态参数】)
#include <pybind11/pybind11.h>
#include <iostream>
#include <string>
namespace py = pybind11;
int my_variable = 42;
int add(int x, int y) {
return x + y;
}
class MyClass {
public:
MyClass(int x, int y) :m_x(x), m_y(y) {}
void printMessage(const std::string& message) {
std::cout << "Message: " << message << std::endl;
}
void printLocation() {
std::cout << "Location: (" << m_x << ", " << m_y << ")" << std::endl;
}
std::string className = "Eliza";
private:
int m_x, m_y;
};
PYBIND11_MODULE(pybindTest, m) {
m.doc() = "test pybind11";
//定义变量
m.attr("name") = "Eliza";
m.attr("age") = 108;
//临时定义函数内容
m.def("get_variable", []() {
return my_variable;
}, "get variable");
m.def("set_variable", [](int value) {
my_variable = value;
}, "set variable");
//事先定义函数内容,通过py::arg设置参数的默认值
//只有前两个参数必填,后3个参数可不填
m.def("add", &add, "A function that adds two numbers",
py::arg("i") = 1, py::arg("j") = 2);
//类,及其带参方法init和printMessage、动态参数name
py::class_<MyClass>(m, "MyClass", py::dynamic_attr())
.def(py::init<int, int>())
.def("printMessage", &MyClass::printMessage)
.def("printLocation", &MyClass::printLocation)
.def_readwrite("name", &MyClass::className);//另一种写法:def_property()
}
2.4 Python代码
import pybindTest
print("name: ", pybindTest.name)
print("age: ", pybindTest.age)
value = pybindTest.get_variable()
print("Variable value: ", value)
pybindTest.set_variable(100)
new_value = pybindTest.get_variable()
print("New variable value: ", new_value)
addNum = pybindTest.add()
print("Default add result: ", addNum)
addNum = pybindTest.add(3, 4)
print("New add result: ", addNum)
myclass = pybindTest.MyClass(1, 3)
myclass.printLocation()
print("myclass name: ", myclass.name)
myclass.name = "Sera"
print("new myclass name: ", myclass.name)
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