python调用C++方法
以下是主要的python绑定cpp的方法:
方法 | 年份 | 代表用户 |
---|---|---|
适用于 CPython 的 C/C++ 扩展模块 | 1991 | 标准库 |
PyBind11(推荐用于 C++) | 2015 | |
Cython(推荐用于 C) | 2007 | gevent、kivy |
HPy | 2019 | |
mypyc | 2017 | |
ctype | 2003 | oscrypto |
cffi | 2013 | cryptography、pypy |
SWIG | 1996 | crfsuite |
Boost.Python | 2002 | |
cppyy | 2017 |
其中
- ctypes: C 与 Python 绑定, Python 内建模块,通过调用.so动态链接库来使用方法,不需要在c++中有特殊的写法,如果使用c++则需要使用extern c的方式再包装一层
- Boost.Python: C++ 与 Python 绑定, Boost 模块
- pybind11: C++11 与 Python 绑定, 减去了旧 C++ 支持,更轻量化,需要在编写cpp时处理响应的绑定函数。
- CPython python标准库中的方式,需要手写绑定方法
一般使用pybind11多一些,因为更为轻量化,能够轻易地加入c++特性。
一、背景
现有个项目需要封装给python用户使用,讨论过后决定使用pybind11进行封装。
二、使用pybind11为python添加c++扩展
首先在安装时确保c++环境和python环境保持一致。
在已经完整安装pybind11的基础上,在c++项目中添加pybind11库,接下来就是使用pybind11编写c++到pybind11的扩展:
1、首先导入pybind11的头文件:
#include <pybind11/numpy.h>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
2、添加方法绑定
PYBIND11_MODULE( 模块名, 模块实例对象 ){
m.doc() = "pybind11 example"; //可选,说明这个模块是做什么的
//封装的具体操作。这些操作包括普通的函数的封装,类的访问等下面用不同例子来说明问题
}
调用普通函数
pybind11的模块实例对象提供了 def()函数,用来封装普通的函数,具体的用法为
def( "给python调用方法名", &实际操作的函数, "函数功能说明" ). //其中函数功能说明为可选
例子如下:
#include <pybind11/numpy.h>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
int mytest( int i, int j ){
....
....
....
}
PYBIND11_MODULE( py2cpp, m ){
m.doc() = "pybind11 example";
m.def("mytest", &mytest, "just test" );
}
//在python中使用 模块名.函数名 来访问
//例如本例子为 py2cpp.mytest(1,2)
对应在python中的访问:
import py2cpp
py2cpp.mytest(1,2)
调用类
pybind11::class_<命名空间::类名>(m, "在python中构造这个类的方法名" )
.def(pybind11<>::init()) //构造器,对应的是c++类的构造函数,如果没有这个构造函数,或者参数对不是会调用失败
.def( "python中函数名", &命名空间::类名::函数名 );
调用命名空间外的类:
#include <pybind11/pybind11.h>
class Hello
{
public:
Hello(){}
void say( const std::string s ){
std::cout << s << std::endl;
}
};
PYBIND11_MODULE( py2cpp, m ){
m.doc() = "pybind11 example";
pybind11::class_<Hello>(m, "Hello" )
.def(pybind11::init())
.def( "say", &Hello::say );
}
//python 调用方式
//1, 先通过构造器来构建实例,方法为 模块名.构造器名
//2,调用对应的方法, 模块名.方法名
//例如本例子需要如下调用
// c=py2cpp.Hello()
// c.say()
调用命名空间中的对象:
#include <pybind11/pybind11.h>
namespace NS{
class World{
public:
World(){}
void say( const std::string s ){
std::cout << s << std::endl;
}
};
PYBIND11_MODULE( py2cpp, m ){
m.doc() = "pybind11 example";
pybind11::class_<NS::World>(m, "World")
.def(pybind11::init())
.def("say", &NS::World::say);
}
//本例子需要如下调用
// c=py2cpp.World()
// c.say()
注意把c++文件编译成.so的编译命令:
g++ -shared -std=c++11 -fPIC `python3 -m pybind11 --includes` 要编译的源代码 -o 模块名`python3-config --extension-suffix` -I /path/to/python3
注意:
在使用pybind11过程中涉及3个地方用模块名,且必须一致,否则会出错:
- c++代码中 PYBIND11_MODULE 后面的
- 编译命令行中的模块名,参见上面
- 在python中import时使用到的模块名
使用cmake进行模块编译
在实际项目中关联的c++文件会很多,建议使用cmake进行编译,最终生成相应模块。
cmake_minimum_required(VERSION 2.8 FATAL_ERROR)
project("my_xxx")
#----------------------------CMAKE & GLOBAL PROPERTIES-------------------------#
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
include_directories(/usr/local/include/opencv4)
###============= C++11 support====================================
add_definitions(-std=c++11)
option(CUDA_USE_STATIC_CUDA_RUNTIME OFF)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_BUILD_TYPE Debug)
if(${CMAKE_VERSION} VERSION_LESS "3.1")
include(CheckCXXCompilerFlag)
CHECK_CXX_COMPILER_FLAG("-std=c++11" COMPILER_SUPPORTS_CXX11)
CHECK_CXX_COMPILER_FLAG("-std=c++0x" COMPILER_SUPPORTS_CXX0X)
if (COMPILER_SUPPORTS_CXX11)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
elseif (COMPILER_SUPPORTS_CXX0X)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x")
else ()
message(FATAL_ERROR "The compiler ${CMAKE_CXX_COMPILER} has no C++11 support. Please use a different C++ compiler.")
endif ()
else()
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
endif()
#=============== Find Packages ====================================
find_package(.....)
include_directories(......)
FIND_LIBRARY(.....)
add_subdirectory(src/pybind11-2.1.1)
pybind11_add_module(my_xxx xxxx.cpp)
如果想在已有 C++ 动态库上扩展 pybind11 绑定,那么 target_link_libraries 链接该动态库就可以了。
target_link_libraries(my_xxx PUBLIC xxxx)
参考: