使用pybind11从c++调用Python函数

18
我想在包含main()函数的C++代码中使用Pybind11调用Python函数。但是我发现很少有相关的参考资料,现有的大多数文档都是关于反向操作,即从Python调用C++。

是否有任何完整的示例展示如何实现这一点? 我找到的唯一参考是:https://github.com/pybind/pybind11/issues/30 ,但其中信息非常有限。

3个回答

26
您的问题实际上有两个方面:一个是关于从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.htmlhttp://pybind11.readthedocs.io/en/master/advanced/embedding.html找到。


1
上面的链接已经失效,请使用下面的链接:
  • https://pybind11.readthedocs.io/en/stable/advanced/embedding.html
- Alessandro Caproni

18

Jason的回答基本上正确,但我想添加一个略微复杂(且干净)的例子,以使用numpy输入来调用Python方法。

  1. 我们可以使用py::reinterpret_borrow<py::function>py::object转换为py::function
  2. 我们可以输入std::vector,它会自动转换为numpy.array

请注意,用户需要确保PyModule.attr实际上是Python函数。还要注意,类型转换适用于各种C++类型(有关详细信息,请参见此处)。

在这个例子中,我想使用方法scipy.optimize.minimize,并使用从C++接口提供的起始点x0

#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

希望这能帮到有需要的人!


嗨,我正在尝试使用你的代码,但是我遇到了一个异常:Microsoft C++ 异常:pybind11::error_already_set at memory。 - Ziri
1
C++代码的最后三行有res.attr,但应该改为result.attr - AlDanial
有没有办法在自动转换过程中避免将数据从STL容器复制到numpy.array中?比如创建一个指向STL容器内存的Python对象... - Rackbox
我尝试找到一种避免从STL复制到Python的方法,但没有找到。 - Romeo Valentin
@Rackbox,您可以使用缓冲区协议来避免复制数据。我不知道std::vector是否已经内置了缓冲区协议,或者您是否需要自己实现它。但是,pybind11测试脚本会对缓冲区协议进行测试,因此您应该能够在那里查找如何使用它的方法。 - Cris Luengo

5
  1. 项目结构
  • CMakeLists.txt
  • calc.py
  • main.cpp
  1. 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")("good luck");
    
        // 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;
    }
    
  2. 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)
    
    
  3. 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)
    

在代码的第py::module_ tokenize = py::module_::import("calc");行抛出了一个异常。 - undefined
需要安装transformers包。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接