【问题标题】:Sending Python function as Boost.Function argument将 Python 函数作为 Boost.Function 参数发送
【发布时间】:2016-03-17 00:29:22
【问题描述】:

在我试图将 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

我之前在 SO 上问过 vaguely similar question 并得到了惊人的答案。我也想知道那里给出的答案中的类似方法是否也可以应用于这个用例。

非常感谢大家的支持!

【问题讨论】:

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


    【解决方案1】:

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

    from-Python 转换器已注册:

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

    测试可转换性和构造对象的步骤发生在两个不同的步骤中。由于没有为 boost::function&lt;void(int)&gt; 注册 from-Python 转换器,Boost.Python 将引发 ArgumentError 异常。 Boost.Python 不会尝试构造 boost::function&lt;void(int)&gt; 对象,尽管 boost::function&lt;void(int)&gt; 可以从 boost::python::object 构造。


    要解决此问题,请考虑使用 shim 函数将 boost::function&lt;void(int)&gt; 的构造推迟到 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&lt;void(int)&gt; 显式注册一个来自Python 的转换器。这样做的好处是 所有 通过 Boost.Python 公开的函数都可以使用转换器(例如,不需要为每个函数编写 shim)。但是,需要为每种 C++ 类型注册转换。这是一个示例demonstratingboost::function&lt;void(int)&gt;boost::function&lt;void(std::string)&gt; 显式注册自定义转换器:

    #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)>()
        ;
    }
    

    【讨论】:

      【解决方案2】:

      一种解决方案是添加一个重载函数:

      void get_async(std::string url, boost::python::object obj)
      {
          if (PyCallable_Check(obj.ptr()))
              get_async(url, static_cast<boost::function<void(int)>>(obj));
      }
      

      然后只公开这个特定的重载:

      .def("get_async", static_cast<void (http_manager::*)(std::string, boost::python::object)>(&http_manager::get_async))
      

      或者,如果你不想用 python 东西污染你的主类,那么你可以创建一个包装类。事情看起来也更干净了:

      struct http_manager_wrapper : http_manager
      {
          void get_async(std::string url, boost::python::object obj)
          {
              if (PyCallable_Check(obj.ptr()))
                  http_manager::get_async(url, obj);
          }
      
      } http_wrapper;
      
      BOOST_PYTHON_MODULE(example)
      {
          boost::python::class_<http_manager_wrapper>("HttpManager", boost::python::no_init)
              .def("get_async", &http_manager_wrapper::get_async);
      
          boost::python::scope().attr("http") = boost::ref(http_wrapper);
      }
      

      更新: 另一种选择是使用可调用的 python 来提升函数转换器。这将解决单例问题,并且不需要更改主类。

      struct http_manager
      {
          void get_async(std::string url, boost::function<void(int)> on_response)
          {
              if (on_response)
              {
                  on_response(42);
              }
          }
      } http;
      
      struct BoostFunc_from_Python_Callable
      {
          BoostFunc_from_Python_Callable()
          {
              boost::python::converter::registry::push_back(&convertible, &construct, boost::python::type_id< boost::function< void(int) > >());
          }
      
          static void* convertible(PyObject* obj_ptr)
          {
              if (!PyCallable_Check(obj_ptr)) 
                  return 0;
              return obj_ptr;
          }
      
          static void construct(PyObject* obj_ptr, boost::python::converter::rvalue_from_python_stage1_data* data)
          {
              boost::python::object callable(boost::python::handle<>(boost::python::borrowed(obj_ptr)));
              void* storage = ((boost::python::converter::rvalue_from_python_storage< boost::function< void(int) > >*) data)->storage.bytes;
              new (storage)boost::function< void(int) >(callable);
              data->convertible = storage;
          }
      };
      
      BOOST_PYTHON_MODULE(example)
      {
          // Register function converter
          BoostFunc_from_Python_Callable();
      
          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);
      }
      

      【讨论】:

      • 不幸的是,在这种情况下,http 对象是全局可访问的,并且本质上是一个单例,因此制作包装器不起作用,我想避免用 Python 实现细节污染类。
      • @ColinBasnett 我的最新更新应该可以解决您提到的问题。
      猜你喜欢
      • 1970-01-01
      • 2020-11-28
      • 2021-06-04
      • 2021-07-07
      • 2015-03-31
      • 1970-01-01
      • 1970-01-01
      • 2011-12-27
      • 1970-01-01
      相关资源
      最近更新 更多