使用 pybind11 从 C++ 调用 Python 函数
call a Python function from c++ using pybind11
我正在尝试从包含 main()
函数的 C++ 代码调用 python 函数,使用 Pybind11
。但是我发现很少有参考资料可用。大多数现有文档都在谈论相反的方向,即从 Python.
调用 C++
有没有完整的例子说明如何做到这一点?我找到的唯一参考是:https://github.com/pybind/pybind11/issues/30
但是它的信息很少。
你的问题的答案实际上有两个部分:一个关于从 C++ 调用 Python 函数,另一个关于嵌入解释器。
在 pybind11 中调用一个函数只是将该函数放入一个 pybind11::object
变量中,您可以在该变量上调用 operator()
来尝试调用该对象。 (它不一定是函数,而只是可调用的东西:例如,它也可以是具有 __call__
方法的对象)。例如,要从 C++ 代码中调用 math.sqrt(2)
,您将使用:
auto math = py::module::import("math");
auto resultobj = math.attr("sqrt")(2);
double result = resultobj.cast<double>();
或者您可以将其全部压缩为:
double result = py::module::import("math").attr("sqrt")(2).cast<double>();
问题的第二部分涉及如何从 C++ 可执行文件执行此操作。在构建可执行文件时(即当您的 C++ 代码包含 main()
时),您必须在二进制文件中嵌入 Python 解释器,然后才能使用 Python 执行任何操作(例如调用 Python 函数)。
嵌入式支持是当前 pybind11 master
分支(将成为 2.2 版本)中添加的新功能。这是一个启动嵌入式 Python 解释器并调用 Python 函数 (math.sqrt
) 的基本示例:
#include <pybind11/embed.h>
#include <iostream>
namespace py = pybind11;
int main() {
py::scoped_interpreter python;
auto math = py::module::import("math");
double root_two = math.attr("sqrt")(2.0).cast<double>();
std::cout << "The square root of 2 is: " << root_two << "\n";
}
输出:
The square root of 2 is: 1.41421
有关调用函数和嵌入的更多示例和文档分别位于 http://pybind11.readthedocs.io/en/master/advanced/pycpp/object.html and http://pybind11.readthedocs.io/en/master/advanced/embedding.html。
Jasons 的回答非常准确,但我想添加一个稍微复杂(和干净)的示例,调用带有 numpy
输入的 python 方法。
我想展示两点:
- 我们可以使用
py::reinterpret_borrow<py::function>
将 py::object
转换为 py::function
- 我们可以输入
std::vector
自动转换为 numpy.array
请注意,用户有责任确保 PyModule.attr
实际上是一个 python 函数。另请注意,类型转换适用于多种 c++
类型(有关详细信息,请参阅 here)。
在这个例子中,我想使用方法 scipy.optimize.minimize
,起始点 x0
由 c++ 接口提供。
#include <iostream>
#include <vector>
#include <pybind11/pybind11.h>
#include <pybind11/embed.h> // python interpreter
#include <pybind11/stl.h> // type conversion
namespace py = pybind11;
int main() {
std::cout << "Starting pybind" << std::endl;
py::scoped_interpreter guard{}; // start interpreter, dies when out of scope
py::function min_rosen =
py::reinterpret_borrow<py::function>( // cast from 'object' to 'function - use `borrow` (copy) or `steal` (move)
py::module::import("py_src.exec_numpy").attr("min_rosen") // import method "min_rosen" from python "module"
);
py::object result = min_rosen(std::vector<double>{1,2,3,4,5}); // automatic conversion from `std::vector` to `numpy.array`, imported in `pybind11/stl.h`
bool success = result.attr("success").cast<bool>();
int num_iters = result.attr("nit").cast<int>();
double obj_value = result.attr("fun").cast<double>();
}
使用 python 脚本 py_src/exec_numpy.py
import numpy as np
from scipy.optimize import minimize, rosen, rosen_der
def min_rosen(x0):
res = minimize(rosen, x0)
return res
希望这对某人有所帮助!
- 项目结构
- CMakeLists.txt
- calc.py
- main.cpp
main.cpp
#include <pybind11/embed.h>
#include <iostream>
namespace py = pybind11;
using namespace py::literals;
int main() {
py::scoped_interpreter guard{};
// append source dir to sys.path, and python interpreter would find your custom python file
py::module_ sys = py::module_::import("sys");
py::list path = sys.attr("path");
path.attr("append")("..");
// import custom python class and call it
py::module_ tokenize = py::module_::import("calc");
py::type customTokenizerClass = tokenize.attr("CustomTokenizer");
py::object customTokenizer = customTokenizerClass("/Users/Caleb/Desktop/codes/ptms/bert-base");
py::object res = customTokenizer.attr("custom_tokenize")("今天的风儿很喧嚣");
// show the result
py::list input_ids = res.attr("input_ids");
py::list token_type_ids = res.attr("token_type_ids");
py::list attention_mask = res.attr("attention_mask");
py::list offsets = res.attr("offset_mapping");
std::string message = "input ids is {},\noffsets is {}"_s.format(input_ids, offsets);
std::cout << message << std::endl;
}
calc.py
from transformers import BertTokenizerFast
class CustomTokenizer(object):
def __init__(self, vocab_dir):
self._tokenizer = BertTokenizerFast.from_pretrained(vocab_dir)
def custom_tokenize(self, text):
return self._tokenizer(text, return_offsets_mapping=True)
def build_tokenizer(vocab_dir: str) -> BertTokenizerFast:
tokenizer = BertTokenizerFast.from_pretrained(vocab_dir)
return tokenizer
def tokenize_text(tokenizer: BertTokenizerFast, text: str) -> dict:
res = tokenizer(text, return_offsets_mapping=True)
return dict(res)
CMakeLists.txt
cmake_minimum_required(VERSION 3.4)
project(example)
set(CMAKE_CXX_STANDARD 11)
# set pybind11 dir
set(pybind11_DIR /Users/Caleb/Softwares/pybind11)
find_package(pybind11 REQUIRED)
# set custom python interpreter(under macos)
link_libraries(/Users/Caleb/miniforge3/envs/py38/lib/libpython3.8.dylib)
add_executable(example main.cpp)
target_link_libraries(example PRIVATE pybind11::embed)
我正在尝试从包含 main()
函数的 C++ 代码调用 python 函数,使用 Pybind11
。但是我发现很少有参考资料可用。大多数现有文档都在谈论相反的方向,即从 Python.
有没有完整的例子说明如何做到这一点?我找到的唯一参考是:https://github.com/pybind/pybind11/issues/30
但是它的信息很少。
你的问题的答案实际上有两个部分:一个关于从 C++ 调用 Python 函数,另一个关于嵌入解释器。
在 pybind11 中调用一个函数只是将该函数放入一个 pybind11::object
变量中,您可以在该变量上调用 operator()
来尝试调用该对象。 (它不一定是函数,而只是可调用的东西:例如,它也可以是具有 __call__
方法的对象)。例如,要从 C++ 代码中调用 math.sqrt(2)
,您将使用:
auto math = py::module::import("math");
auto resultobj = math.attr("sqrt")(2);
double result = resultobj.cast<double>();
或者您可以将其全部压缩为:
double result = py::module::import("math").attr("sqrt")(2).cast<double>();
问题的第二部分涉及如何从 C++ 可执行文件执行此操作。在构建可执行文件时(即当您的 C++ 代码包含 main()
时),您必须在二进制文件中嵌入 Python 解释器,然后才能使用 Python 执行任何操作(例如调用 Python 函数)。
嵌入式支持是当前 pybind11 master
分支(将成为 2.2 版本)中添加的新功能。这是一个启动嵌入式 Python 解释器并调用 Python 函数 (math.sqrt
) 的基本示例:
#include <pybind11/embed.h>
#include <iostream>
namespace py = pybind11;
int main() {
py::scoped_interpreter python;
auto math = py::module::import("math");
double root_two = math.attr("sqrt")(2.0).cast<double>();
std::cout << "The square root of 2 is: " << root_two << "\n";
}
输出:
The square root of 2 is: 1.41421
有关调用函数和嵌入的更多示例和文档分别位于 http://pybind11.readthedocs.io/en/master/advanced/pycpp/object.html and http://pybind11.readthedocs.io/en/master/advanced/embedding.html。
Jasons 的回答非常准确,但我想添加一个稍微复杂(和干净)的示例,调用带有 numpy
输入的 python 方法。
我想展示两点:
- 我们可以使用
py::reinterpret_borrow<py::function>
将 - 我们可以输入
std::vector
自动转换为numpy.array
py::object
转换为 py::function
请注意,用户有责任确保 PyModule.attr
实际上是一个 python 函数。另请注意,类型转换适用于多种 c++
类型(有关详细信息,请参阅 here)。
在这个例子中,我想使用方法 scipy.optimize.minimize
,起始点 x0
由 c++ 接口提供。
#include <iostream>
#include <vector>
#include <pybind11/pybind11.h>
#include <pybind11/embed.h> // python interpreter
#include <pybind11/stl.h> // type conversion
namespace py = pybind11;
int main() {
std::cout << "Starting pybind" << std::endl;
py::scoped_interpreter guard{}; // start interpreter, dies when out of scope
py::function min_rosen =
py::reinterpret_borrow<py::function>( // cast from 'object' to 'function - use `borrow` (copy) or `steal` (move)
py::module::import("py_src.exec_numpy").attr("min_rosen") // import method "min_rosen" from python "module"
);
py::object result = min_rosen(std::vector<double>{1,2,3,4,5}); // automatic conversion from `std::vector` to `numpy.array`, imported in `pybind11/stl.h`
bool success = result.attr("success").cast<bool>();
int num_iters = result.attr("nit").cast<int>();
double obj_value = result.attr("fun").cast<double>();
}
使用 python 脚本 py_src/exec_numpy.py
import numpy as np
from scipy.optimize import minimize, rosen, rosen_der
def min_rosen(x0):
res = minimize(rosen, x0)
return res
希望这对某人有所帮助!
- 项目结构
- CMakeLists.txt
- calc.py
- main.cpp
main.cpp
#include <pybind11/embed.h> #include <iostream> namespace py = pybind11; using namespace py::literals; int main() { py::scoped_interpreter guard{}; // append source dir to sys.path, and python interpreter would find your custom python file py::module_ sys = py::module_::import("sys"); py::list path = sys.attr("path"); path.attr("append")(".."); // import custom python class and call it py::module_ tokenize = py::module_::import("calc"); py::type customTokenizerClass = tokenize.attr("CustomTokenizer"); py::object customTokenizer = customTokenizerClass("/Users/Caleb/Desktop/codes/ptms/bert-base"); py::object res = customTokenizer.attr("custom_tokenize")("今天的风儿很喧嚣"); // show the result py::list input_ids = res.attr("input_ids"); py::list token_type_ids = res.attr("token_type_ids"); py::list attention_mask = res.attr("attention_mask"); py::list offsets = res.attr("offset_mapping"); std::string message = "input ids is {},\noffsets is {}"_s.format(input_ids, offsets); std::cout << message << std::endl; }
calc.py
from transformers import BertTokenizerFast class CustomTokenizer(object): def __init__(self, vocab_dir): self._tokenizer = BertTokenizerFast.from_pretrained(vocab_dir) def custom_tokenize(self, text): return self._tokenizer(text, return_offsets_mapping=True) def build_tokenizer(vocab_dir: str) -> BertTokenizerFast: tokenizer = BertTokenizerFast.from_pretrained(vocab_dir) return tokenizer def tokenize_text(tokenizer: BertTokenizerFast, text: str) -> dict: res = tokenizer(text, return_offsets_mapping=True) return dict(res)
CMakeLists.txt
cmake_minimum_required(VERSION 3.4) project(example) set(CMAKE_CXX_STANDARD 11) # set pybind11 dir set(pybind11_DIR /Users/Caleb/Softwares/pybind11) find_package(pybind11 REQUIRED) # set custom python interpreter(under macos) link_libraries(/Users/Caleb/miniforge3/envs/py38/lib/libpython3.8.dylib) add_executable(example main.cpp) target_link_libraries(example PRIVATE pybind11::embed)