python - 将 Python 函数作为 Boost.Function 参数发送

标签 python c++ boost boost-python boost-function

在我尝试将 Python 代码与 C++ 结合起来的世界中,事情变得越来越复杂。

本质上,我希望能够分配一个在 HTTP 调用收到响应后使用的回调函数,并且我希望能够从 C++ 或 Python 中执行此操作。

换句话说,我希望能够从 C++ 调用它:

http.get_asyc("www.google.ca", [&](int a) { std::cout << "response recieved: " << a << std::endl; });

这是来自 Python 的:

def f(r):
    print str.format('response recieved: {}', r)

http.get_async('www.google.ca', f)

我已经设置了demo on Coliru这正是我想要实现的目标。这是我收到的代码和错误:

C++

#include <boost/python.hpp>
#include <boost/function.hpp>

struct http_manager
{
    void get_async(std::string url, boost::function<void(int)> on_response)
    {
        if (on_response)
        {
            on_response(42);
        }
    }
} http;

BOOST_PYTHON_MODULE(example)
{
    boost::python::class_<http_manager>("HttpManager", boost::python::no_init)
        .def("get_async", &http_manager::get_async);

    boost::python::scope().attr("http") = boost::ref(http);
}

Python

import example
def f(r):
    print r
example.http.get_async('www.google.ca', f)

错误

Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
Boost.Python.ArgumentError: Python argument types in
    HttpManager.get_async(HttpManager, str, function)
did not match C++ signature:
    get_async(http_manager {lvalue}, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, boost::function<void (int)>)

我不确定为什么 function 没有自动转换为 boost::function

我问过vaguely similar question之前就这样,得到了一个惊人的答案。我还想知道答案中给出的类似方法是否也可以应用于此用例。

非常感谢大家的支持!

最佳答案

当调用通过 Boost.Python 公开的函数时,Boost.Python 将查询其注册表,根据所需的 C++ 类型为调用者的每个参数找到合适的 from-Python 转换器。如果找到知道如何从 Python 对象转换为 C++ 对象的转换器,那么它将使用该转换器来构造 C++ 对象。如果没有找到合适的转换器,则 Boost.Python 将引发 ArgumentError异常。

from-Python 转换器已注册:

  • 对于 Boost.Python 支持的类型自动执行,例如 intstd::string
  • 隐式用于 boost::python::class<T> 公开的类型。默认情况下,生成的 Python 类将包含 T 的嵌入实例。 C++ 对象,并为 Python 类和类型注册 to-Python 和 from-Python 转换器 T ,使用嵌入式实例。
  • 明确通过 boost::python::converter::registry::push_back()

测试可转换性和构造对象的步骤分为两个不同的步骤。由于 boost::function<void(int)> 尚未注册任何 from-Python 转换器。 ,Boost.Python 将引发 ArgumentError异常(exception)。 Boost.Python 不会尝试构造 boost::function<void(int)>对象,尽管boost::function<void(int)>可以从 boost::python::object 构建.


要解决此问题,请考虑使用填充函数来推迟 boost::function<void(int)> 的构造直到boost::python::object之后已经通过了Boost.Python层:

void http_manager_get_async_aux(
  http_manager& self, std::string url, boost::python::object on_response)
{
  return self.get_async(url, on_response);
}

...

BOOST_PYTHON_MODULE(example)
{
  namespace python = boost::python;
  python::class_<http_manager>("HttpManager", python::no_init)
    .def("get_async", &http_manager_get_async_aux);

  ...
}

这是一个完整的示例 demonstrating这种方法:

#include <boost/python.hpp>
#include <boost/function.hpp>

struct http_manager
{
  void get_async(std::string url, boost::function<void(int)> on_response)
  {
    if (on_response)
    {
      on_response(42);
    }
  }
} http;

void http_manager_get_async_aux(
  http_manager& self, std::string url, boost::python::object on_response)
{
  return self.get_async(url, on_response);
}

BOOST_PYTHON_MODULE(example)
{
  namespace python = boost::python;
  python::class_<http_manager>("HttpManager", python::no_init)
    .def("get_async", &http_manager_get_async_aux);

  python::scope().attr("http") = boost::ref(http);
}

交互式使用:

>>> import example
>>> result = 0
>>> def f(r):
...     global result
...     result = r
...
>>> assert(result == 0)
>>> example.http.get_async('www.google.com', f)
>>> assert(result == 42)
>>> try:
...     example.http.get_async('www.google.com', 42)
...     assert(False)
... except TypeError:
...    pass
...

另一种方法是显式注册 boost::function<void(int)> 的 from-Python 转换器。 。这样做的好处是,通过 Boost.Python 公开的所有函数都可以使用转换器(例如,不需要为每个函数编写填充程序)。但是,需要为每个 C++ 类型注册一个转换。这是一个例子demonstrating显式注册 boost::function<void(int)> 的自定义转换器和boost::function<void(std::string)> :

#include <boost/python.hpp>
#include <boost/function.hpp>

struct http_manager
{
  void get_async(std::string url, boost::function<void(int)> on_response)
  {
    if (on_response)
    {
      on_response(42);
    }
  }
} http;

/// @brief Type that allows for registration of conversions from
///        python iterable types.
struct function_converter
{
  /// @note Registers converter from a python callable type to the
  ///       provided type.
  template <typename FunctionSig>
  function_converter&
  from_python()
  {
    boost::python::converter::registry::push_back(
      &function_converter::convertible,
      &function_converter::construct<FunctionSig>,
      boost::python::type_id<boost::function<FunctionSig>>());

    // Support chaining.
    return *this;
  }

  /// @brief Check if PyObject is callable.
  static void* convertible(PyObject* object)
  {
    return PyCallable_Check(object) ? object : NULL;
  }

  /// @brief Convert callable PyObject to a C++ boost::function.
  template <typename FunctionSig>
  static void construct(
    PyObject* object,
    boost::python::converter::rvalue_from_python_stage1_data* data)
  {
    namespace python = boost::python;
    // Object is a borrowed reference, so create a handle indicting it is
    // borrowed for proper reference counting.
    python::handle<> handle(python::borrowed(object));

    // Obtain a handle to the memory block that the converter has allocated
    // for the C++ type.
    typedef boost::function<FunctionSig> functor_type;
    typedef python::converter::rvalue_from_python_storage<functor_type>
                                                                storage_type;
    void* storage = reinterpret_cast<storage_type*>(data)->storage.bytes;

    // Allocate the C++ type into the converter's memory block, and assign
    // its handle to the converter's convertible variable.
    new (storage) functor_type(python::object(handle));
    data->convertible = storage;
  }
};

BOOST_PYTHON_MODULE(example)
{
  namespace python = boost::python;
  python::class_<http_manager>("HttpManager", python::no_init)
    .def("get_async", &http_manager::get_async);

  python::scope().attr("http") = boost::ref(http);

  // Enable conversions for boost::function.
  function_converter()
    .from_python<void(int)>()
    // Chaining is supported, so the following would enable
    // another conversion.
    .from_python<void(std::string)>()
    ;
}

关于python - 将 Python 函数作为 Boost.Function 参数发送,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36049533/

相关文章:

python - 如何在我的红帽服务器上运行 python 文件?

python - 在 MySQL blob 中插入 python 二进制字符串对象

python - 为什么在 Python 3.8 中是 sqrt(x*x + y*y) != math.hypot(x, y)?

python - 合并两个 DataFrame 的函数

c++ - 使用 dst with boost 将当前服务器时间转换为时区

c++ - Boost变体模糊构造

c++ - 理解 float 的二进制表示

c++ - MPI_Bcast 错误

java - Android、JNI在C++中调用java构造函数

c++ - 如何确定 (x, y) 点是否在由边界点列表定义的多边形内