python invoking c++
利用Boost.Python和pybind11可在Python中调用C++模块。Boost.Python成熟稳定,需手动处理类型转换与生命周期;pybind11语法现代简洁,支持自动处理结构体和成员暴露。两种方案均需编写包装代码并链接对应库,编译命令简洁,调用方式接近原生Python对象。
在Python中调用C++模块,这事说难不难,说简单也不简单。难在跨语言调用的坑太多,比如类型转换、对象生命周期、异常处理,一不小心就踩进去。简单在像Boost.Python和pybind11这样的工具已经相当成熟,把C++代码暴露给Python,很多时候就是写一个wrapper的事。
下面这份材料,就是把两种方案的「最小可行」路径给你跑了一遍,从代码到构建,清晰明了。
invoking c/c++ module from python
我们先从老牌工具Boost.Python看起。
boost python
Boost.Python的功力是经过时间验证的,很多老项目都在用它。下面这个代码就是一个典型的封装示例,把底层的C++接口 c_module.h 优雅地包装成了Python可调用对象。
code
核心的 wrapper 文件 model_wrapper.cpp。注意看上面对 write 函数的处理——它用了 BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS 宏,允许在Python里传2个或3个参数,第三个参数 expire 自带默认值0。这种细节,恰恰是生产级代码里需要的。
#include
#include
#include
#include
#include
#include "module_py.h"
using namespace boost::python;
BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(write_overloads, MyModulePy::write, 2, 3);
BOOST_PYTHON_MODULE(module_py)
{
boost::python::scope().attr("__version__") = "v20180123.1";
class_("MyModulePy")
.def("read", &MyModulePy::read)
.def("batch_read", &MyModulePy::batch_read)
.def("write_read", &MyModulePy::batch_write)
.def("write", &MyModulePy::write, write_overloads(args("key", "value", "expire"), "put key-value with a expire time"))
.def("remove", &MyModulePy::remove)
.def("config", &MyModulePy::config)
;
}
好,那接下来一个问题就是,MyModulePy 这个Python类是怎么对接底层C++库的?module_py.h 给出了答案。这里有一点值得留意:batch_read 和 batch_write 中,作者手动处理了字典和列表的转换。这是最绕也是最容易出错的地方——记住,迭代Python字典时,千万不要用 const std::string& k = ... 这种引用捕获方式,否则极大概率会踩到生命周期问题的坑。
#include "c_module.h" // header of libmodule.so
struct MyModulePy
{
MyModulePy() {}
std::string read(const std::string& key)
{
// do read
return mModule.Read(key);
}
boost::python::dict batch_read(const boost::python::list& keys)
{
std::vector ckeys(boost::python::len(keys));
for (int i = 0; i < boost::python::len(keys); i++)
{
ckeys[i] = boost::python::extract(keys[i]);
}
std::map cvalues = mModule.BatchRead(ckeys);
boost::python::dict values;
for (auto it = cvalues.begin(); it != cvalues.end(); ++it)
{
values[it->first] = it->second;
}
return values;
}
void bach_write(const boost::python::dict& keyValues)
{
auto skeys = keyValues.keys();
// http://www.boost.org/doc/libs/1_34_0/libs/python/doc/v2/dict.html
int num = boost::python::len(skeys);
std::map datas;
for (int i = 0; i < num; i++) // TODO: how to iterate a boost::python::dict ?
{
string k = boost::python::extract(skeys[i]);
// NOTE: do not use 'const string& k = '
string v = boost::python::extract(subKeyValues[skeys[i]]);
datas[k] = v;
}
return mModule.batch_write(datas);
}
void write(const std::string& key, const std::string& value, const int expire = 0)
{
// do write
mModule.Write(key, value, expire);
}
void remove(const std::string& key)
{
// do remove
mModule.Delete(key);
}
std::string config()
{
return mModule.GetConfigInfo();
}
Module mModule; // class provided by libmodule.so
};
至于Python端的使用方式,就非常自然了。实例化MyModulePy,调用读写,就像在操作一个纯粹的Python对象。
import time
from module_py import MyModulePy
m = MyModulePy()
key = 'testkey_%s' % time.time()
value = 'testvalue_%s' % time.time()
try:
print 'config:', m.config()
m.write(key, value)
m.write(key, value, 10)
print 'write:', key, value
print 'read:', key, m.read(key)
print 'delete:', key, m.remove(key)
for i in xrange(0, 3):
mytair.write('batch-%s' % i, str(i))
keys = ['batch-%s' % i for i in xrange(0, 3)]
print mytair.batch_read(keys)
for each in keys:
mytair.remove(each)
print 'read:', key, m.read(key) # exception
except Exception, e:
print e
build
编译命令很简单,关键在于链接 -lboost_python 和你的本地库。
g++ model_wrapper.cpp -o module_py.so -shared -lmodule -lboost_python
pybind11
说完老兵,再来聊聊新锐。pybind11 的语法更现代,上手也更快。项目结构也非常清晰。
layout
/home/admin/pybind11_sample/
|---- py_sample.cpp
|---- sample.py
|---- build.sh
|---- include/
| |---- pybind11/
| i installed pybind11 at here
| |---- text_processor/
| c++ module
code
py_sample.cpp 里,你会发现 pybind11 用起来要简洁得多。比如 .def_readonly 直接暴露C++成员变量为Python只读属性,省去了写getter的麻烦。返回值是一个自定义结构体 InfoWrapper,pybind11 也能自动处理。
class TextProcessorWrapper
{
public:
struct InfoWrapper
{
std::string simhash_code;
std::string ancestor_code;
};
public:
TextProcessorWrapper(const std::string& data_dir)
{
std::map conf;
conf["data_dir"] = data_dir;
if (!mTextDedup.Init(conf))
{
throw std::runtime_error("failed to init");
}
}
~TextProcessorWrapper()
{
}
InfoWrapper process(const std::string& text, const std::string& title)
{
TextInfo info;
mTextProcessor.Process(text, title, info);
InfoWrapper _info;
_info.simhash_code = info.simhashCode;
_info.ancestor_code = info.ancestorCode;
return _info;
}
protected:
TextProcessor mTextProcessor; // define in libtext_processor_st.a
};
PYBIND11_MODULE(py_sample, m) {
m.doc() = "text processor"; // optional module docstring
// apy class, __init__(), process()
py::class_(m, "TextProcessor")
.def(py::init())
.def("process", &TextProcessorWrapper::process);
// py class, read only attributes
py::class_(m, "TextInfo")
.def_readonly("simhash_code", &TextProcessorWrapper::InfoWrapper::simhash_code)
.def_readonly("ancestor_code", &TextProcessorWrapper::InfoWrapper::ancestor_code);
}
同样地,看看Python侧是怎么用的——创建一个 TextProcessor 实例,传入数据目录,然后调用 process 就能拿到 simhash_code 和 ancestor_code,非常直观。
#!/usr/bin/env python
# -*- coding=utf-8 -*-
import os
import py_textdedup
data_dir = './data'
dedup = py_sample.TextProcessor(data_dir)
info1 = dedup.process("成都—成温邛高速—温江到寿安的丁字路口左转—成青快速通道—过金马河第一个十字路口右转直行即到",
"成都看银杏了")
print info.simhash_code, info.ancestor_code
build
pybind11 的编译命令同样不复杂,核心是链接你的静态库 ltext_processor_st 和指定 -std=c++14。
g++ py_sample.cpp -o py_sample.so -O3 -Wall -shared -std=c++14 -fPIC -I ./include -I /usr/local/include/python2.7 -ltext_processor_st -Wl,--rpath=$$ORIGIN/libs -Wl,--rpath=$$ORIGIN
ref
如果想深入了解 pybind11,官方文档是最好的老师。另外,网上也有不少中英文的实战笔记可以参考,基本能覆盖从零到一的全部问题。
references
最后,把 Boost.Python 的官方文档链接也列一下,方便查证一些更高级的特性,比如重载、索引套件等。
Windows 10 是一款微软推出的经典操作系统,拥有硬件兼容性与多任务处理能力。它更偏向把系统状态查看和常用调节动作放在一起,适合需要持续观察和微调设备状态的场景。
极度公式是一款跨平台专业LaTeX公式识别编辑软件,支持OCR公式识别和多平台编辑。和使用说明,避免使用,享受完整功能与稳定支持。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。
















