boost.python对c++很友好,甚至能直接将python的一些数据结构 list(列表),dict(字典)和相互嵌套等数据传输到c++,boost.numpy也方便python的数组传输到c++端,有时c++端还能直接调用python的函数(很方便调试一些python有的库而windows编译困难的库,相对来说很多库在linux上编译总是友好些),本人也是因为任务需要处理list、dict和其相互嵌套才尝试弄这个的(boost里面不支持set集合,不过自己试过set强制转换为list好像也是可行的),好东西用起来就很喜欢。
linux上好像编译安装一些库都很容易,自己习惯用vs调试代码,一般尽量windows和linux都安装,只是通过boost.python 编写python库调用,相对来说简单点。
注意:个人是调用boost的静态库,动态库没弄成功过,也没时间、不太想去尝试了,重点是按需先实现功能。
linux下需带上cxxflags=-fPIC cflags=-fPIC编译选项,在解压boost源文件后,本人虚拟机上利用anaconda安装了python3.8环境,用的是 boost_1_79_0的源码(这个好像需要指定的python版本),切换到python3.8环境下,而且该环境下安装了numpy,类似如下:
./bootstrap.sh
sudo ./b2 install cxxflags=-fPIC cflags=-fPIC -j8
windows下也要配置好python3.8的环境,里面也要安装numpy,自己已经配置了环境变量,最终的python库也是用该环境的,只是编译boost库的话还算顺利,自己是编译了的,先前下载了一个自带动态、静态boost库的版本,解压就有,boost_1_74_0/lib64-msvc-14.2 里面没有libboost_numpy38的库,要用numpy传多维数组则只好自己编译了,就有了libboost_numpy38-vc142-mt-x64-1_79.lib 库;指令好像挺简单,可以两次双击bootstrap.bat,和bootstrap.bat生成的b2.exe(不配置参数的话,甚至32位的库也给编译了):
个人主要通过cmake,vs2019、anaconda和pycharm等实现方便的c++代码编写生成python库和python环境联调。通过不断查找资料、尝试才实现的,当然能实现大部分也是通过查找资料弄出来的,也谢谢他人的分享,所以自己记录下,提供简化的例子也是方便自己也方便他人,能实现双赢的伙伴或者工作、竞争等关系是最好的,最终实现不了双赢的总会双亏或者失去本该能赢的部分,只是时间问题。
多层cmakelist文件布局确实好用,以前看过人家开源代码是这样的,自己尝试用,发现真好用,例子用的HelloWorld.cpp文件和对应的CMakeLists.txt文件放到了子目录“01_HelloWorld”下,通过外层cmakelist文件ADD_SUBDIRECTORY(01_HelloWorld)包含子目录“01_HelloWorld”。
注意windows下的cmakelist文件里面:set(BOOST_ROOT "修改为你们自己的boost目录”),linux下安装boost会自动配置环境变量之类的,注册这块就行,下面注释掉的可以不用管,都是自己不断尝试的结果。
project(Boost_Test)
cmake_minimum_required(VERSION 3.17)
# find python
find_package(PythonInterp REQUIRED)
find_package(PythonLibs ${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR} EXACT REQUIRED)
# now search for the boost component
# depending on the boost version it is called either python,
# python2, python27, python3, python36, python37, ...
list(
APPEND _components
python${PYTHON_VERSION_MAJOR}${PYTHON_VERSION_MINOR}
python${PYTHON_VERSION_MAJOR}
python
)
#set(_Boost_NUMPY_DEPENDENCIES python${component_python_version})
set(BOOST_ROOT "D:/software/app2/Third-party_libraries/boost/boost_1_79_0")
#set(Boost_INCLUDE_DIR "D:/software/app2/Third-party_libraries/boost/1.74.0/boost")
#set(Boost_LIBRARIES "D:/software/app2/Third-party_libraries/boost/1.74.0/stage/lib")
message(BOOST_ROOT " ${BOOST_ROOT}")
set(_boost_python_found "")
#set(Boost_NAMESPACE "libboost")
set(Boost_USE_MULTITHREADED ON)
set(Boost_USE_STATIC_LIBS ON)
#set(Boost_USE_STATIC_RUNTIME OFF)
foreach(_component IN ITEMS ${_components})
find_package(Boost COMPONENTS ${_component})
if(Boost_FOUND)
set(_boost_python_found ${_component})
break()
endif()
endforeach()
#if(_boost_python_found STREQUAL "")
# message(FATAL_ERROR "No matching Boost.Python component found")
#endif()
#
find_package(Boost REQUIRED COMPONENTS
numpy38
)
include_directories("${PYTHON_INCLUDE_DIRS}")
include_directories("${Boost_INCLUDE_DIRS}")
message(PYTHON_INCLUDE_DIRS " ${PYTHON_INCLUDE_DIRS}")
message(PYTHON_LIBRARIES " ${PYTHON_LIBRARIES}")
message(Boost_INCLUDE_DIRS " ${Boost_INCLUDE_DIRS}")
message(Boost_LIBRARIES " ${Boost_LIBRARIES}")
ADD_SUBDIRECTORY(01_HelloWorld)
这个也是找别人资料基础上修改的。#set(CMAKE_LIBRARY_OUTPUT_DIRECTORY “配置输出python库的路径很有用”) ,这个配置库生成路径挺方便的,个人在linux下有用,cmakelist配置文件内部如果不特意指定的话默认生成的是python动态库,windows上修改后缀为.pyd,linux上后缀.so,个人测试过,将生成的库放到需要调用该库的python文件夹下就可以,个人生成的都是python动态库(虽然调用的boost用的是静态库)。
set(MODULE_NAME hello_world)
# 设置静态库文件目录
#set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
# 动态库文件目录
#set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
# 可执行文件目录
#set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
include_directories(${CMAKE_SOURCE_DIR})
add_library(${MODULE_NAME} SHARED
HelloWorld.cpp
)
if (UNIX)
set_target_properties(${MODULE_NAME}
PROPERTIES
PREFIX ""
)
elseif (WIN32)
set_target_properties(${MODULE_NAME}
PROPERTIES
SUFFIX ".pyd"
)
endif()
target_link_libraries(${MODULE_NAME}
${Boost_LIBRARIES}
${PYTHON_LIBRARIES}
)
需要注意的是BOOST_PYTHON_MODULE(hello_world)里面hello_world是模块名,这个需要和里层cmakelist里面的set(MODULE_NAME hello_world)相对应。具体的生成的python类名或者函数名不需要和c++定义时候一模一样,肯定需要做对应的映射,这就是BOOST_PYTHON_MODULE里面做的事情,配置就好,这个比自己先前利用swig做python库方便太多了。
#define BOOST_PYTHON_STATIC_LIB
#define BOOST_NUMPY_STATIC_LIB
#include <iostream>
#include <boost/python.hpp>
#include <boost/python/numpy.hpp>
//#include
//#include
//#include
namespace bp = boost::python;
namespace np = boost::python::numpy;
//using namespace bp;
bp::list list_reverse(bp::list& list1)
{
std::cout << "Input list length c++ reverse: " << len(list1) << std::endl;
list1.reverse();
return list1;
}
bp::dict dict_test(bp::dict& dt1)
{
std::cout << "Input dict length : " << len(dt1) << std::endl;
bp::list temKeys = dt1.keys();
bp::list temValues = dt1.values();
try {
//Py_Initialize();//VS 嵌入python时调用初始化
for (int i = 0; i < len(dt1); i++)
{
bp::object msg = "%s : %s" % bp::make_tuple(temKeys[i], temValues[i]);
std::string dt1String = bp::extract<std::string>(msg);
std::cout << dt1String << std::endl;
//bp::object ignored = bp::exec("print('c++?')");//执行python代码 ?
}
//Py_Finalize();VS 嵌入python时调用结束 end?
}
catch (bp::error_already_set) {
PyErr_Print();
}
if (dt1.has_key("a"))
{
std::string dt1String = bp::extract<std::string>(dt1["a"]);//key "a"需要存在,才能正常调用
std::cout << "dict has_key a:" << dt1String << std::endl;
}
else
{
dt1.setdefault("a", "default");
dt1.setdefault("float_pi", 3.1415926);
}
return dt1;
}
void list_maps(bp::list& list2)
{
auto a= list2[0];
for (size_t i = 0; i < len(list2); i++)
{
a = list2[i];
bp::dict dic = bp::extract<bp::dict>(a);
bp::list temKeys = dic.keys();
bp::list temValues = dic.values();
std::cout << "list :"<<i << std::endl;
for (int j = 0; j < len(dic); j++)
{
bp::object msg = "%s : %s" % bp::make_tuple(temKeys[j], temValues[j]);
std::string dt1String = bp::extract<std::string>(msg);
std::cout << dt1String << " ";
}
std::cout << "" << std::endl;
}
}
void two_dimensional_array(np::ndarray & ndArray1)
{
//ndArray1.get_shape();
int shapeSize = ndArray1.get_nd();//len(ndArray1);
std::cout << "shapeSize :" << shapeSize << std::endl;
if (2 != shapeSize)
{
std::cout << "ndarray is not two dimension ,but is:" << shapeSize << std::endl;
return;
}
auto shapePtr= ndArray1.get_shape();
auto strdPtr = ndArray1.get_strides();
std::cout << "shape is :[" << shapePtr[0]<<","<< shapePtr[1]<<"]" << std::endl;
std::cout << "get_strides is :[" << strdPtr[0]<<","<< strdPtr[1]<<"]" << std::endl;
int step = strdPtr[0] / strdPtr[1];
std::cout << "step is :[" << step << "]" << std::endl;
float* npF= (float*)ndArray1.get_data();//浮点类型的话,strdPtr[1]=4(4个字节),strdPtr[0]/strdPtr[1]==shapePtr[1](列数)
for (size_t i = 0; i < shapePtr[0]* shapePtr[1]; i++)
{
npF[i] *= npF[i];//square
}
}
char const* greet()
{
return "hello, world,are you ready";
}
float cppCallPythonLogaddexp(PyObject* func, int a, int b)
{
//可不返回,目的是c++端调用python函数获得结果,比如某些函数很难通过编译c++的库进而调用(比如windows编译难,linux编译简单,则windows上临时调试可采用此方法,待移植到linux上用相应的c++库替换则可)
float result = bp::call<float>(func, a, b);
std::cout << "cppCallPythonLogaddexp(" << a << "," << b << ")=" << result << std::endl;
return result;
}
class Cpp2PythonClass
{
public:
Cpp2PythonClass(PyObject* func1) :cppLogaddexp(func1) {};
~Cpp2PythonClass() {};
void callbackFun1Test() {
int a1 = 55;
int a2 = 66;
float result2 = bp::call<float>(cppLogaddexp, a1, a2);//可保存函数func到本地,根据自己需要调用
std::cout << "cppCallPythonLogaddexp(" << a1 << "," << a2 << ")=" << result2 << std::endl;
};
private:
PyObject* cppLogaddexp;
};
BOOST_PYTHON_MODULE(hello_world)
{
using namespace boost::python;
np::initialize();//boost::python::numpy;需要用到
boost::python::def("greet", greet);//python调用名字和c++可不一致,但参数需要保持一致
def("cppCallPythonLogaddexp", cppCallPythonLogaddexp);//好像不加return_value_policy()也可以
def("list_reverse", list_reverse, args("list1"), return_value_policy<return_by_value>());
def("dict_test", dict_test, args("dt1"), return_value_policy<return_by_value>());
def("list_maps", list_maps);
def("two_dimensional_array", two_dimensional_array);
class_<Cpp2PythonClass>("Cpp2PythonClass", boost::python::init<PyObject*>())
.def("callbackFun1Test", &Cpp2PythonClass::callbackFun1Test);
}
自己是配置好cmakelist文件和c++等文件后,用cmake-gui.exe生产vs工程,然后配置vs工程里面库输出路径(放到需要调用该库的python文件下,如自己的test_boost_python_api.py),cmakelist文件没修改的话其实就不用cmake-gui.exe来配置了,每次修改cpp文件就可以了,vs上生成库就好。
test_boost_python_api.py文件内容如下,一些简单测试,比如传递list、dict和嵌套的数据,c++调用python的简单测试。
#!/usr/bin/env python
import sys
import numpy as np
import hello_world
def test1():
l1 = ["df", "df2", "56", 1]
l2 = hello_world.list_reverse(l1)
hello_world.dict_test({"a": "aaa", "b": "222"})
# print(l2)
# print(hello_world.greet())
list_dict = [
{'g1': '545', 'h2': '什么', 'p1': -1.26, 'p2': -1.65854,'p3': -9.56447},
{'g1': '545', 'h2': '纳尼', 'p1': -5.65, 'p2': -8.15,'p3': -63.15},
{'g1': '545', 'h2': 'what?', 'p1': -15.9414, 'p2': -4.3564,'p3': -56.348},
]
hello_world.list_maps(list_dict)
def test2():
np1 = np.arange(15, dtype="float32").reshape(3, 5)
print(type(np1), np1.dtype)
print(np1)
hello_world.two_dimensional_array(np1)
print(np1)
def test3():
dic1 = {"a1": "aaa", "b": "222"}
hello_world.dict_test(dic1)
print(dic1)
print(type(dic1["float_pi"]))
a2 = hello_world.cppCallPythonLogaddexp(np.logaddexp, 2, 3)
print("a2==", a2)
a1 = np.logaddexp(2, 3)
print("python call np.logaddexp(2, 3)==", a1)
print("\n----------------------Cpp2PythonClass----------------------------")
cpc_obj = hello_world.Cpp2PythonClass(np.logaddexp)
cpc_obj.callbackFun1Test()
print("----------------------Cpp2PythonClass.callbackFun1Test ----------------------------\n")
a3 = np.logaddexp(55, 66)
print("python call np.logaddexp(55, 66)==", a3)
if __name__ == '__main__':
test1()
pass
以上做一些记录,不一定都对,个人很多时候都是尝试并加上自己的一些理解才弄出来的,对需要的人应该总有些启发和帮助的,长时间不用的东西肯定会忘记的,所以需要积累和记录,不过一旦看了记录就大致能明白怎么做了。评论一般不回的,谢谢,愿世界恶意少点,对自己可以严格点,但对别人需要宽容点,坏情绪会传染的。
人生需要多点尝试,也需时刻反省。交际、勇气、认知、格局等等限制了个人的能力,自己也是很菜的,搞工程、软件这么多年,没做太深、交际太差、弄得处境很难看,虽然还在挣扎,最后怕是难免转行,也不想抱怨什么,也不想评价别人,且行且珍惜,能通过分享节约别人的时间也好,什么都从头开始很不现实、也太难了,孤军奋战基本注定悲剧的…