2016-03-17 100 views
2

在我嘗試使用C++對Python代碼進行網格劃分的世界裏,事情變得越來越複雜。發送Python函數作爲Boost.Function參數

本質上,我希望能夠分配一個回調函數,以便在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,並得到了一個驚人的答案。我也想知道在這裏給出的答案中的類似方法是否也可以應用於這個用例。

非常感謝您的支持!

回答

2

當調用已經通過Boost.Python的公開的函數,Boost.Python的將查詢其註冊表查找合適的從的Python轉換器的每一個基於所需的C++型呼叫者的參數。如果發現一個轉換器知道如何將Python對象轉換爲C++對象,那麼它將使用轉換器來構造C++對象。如果找不到合適的轉換器,則Boost.Python將引發一個ArgumentError異常。

的從的Python轉換器被註冊:

  • 自動供Boost.Python的支持的類型,如intstd::string
  • 隱含用於通過boost::python::class<T>露出的類型。默認情況下,生成的Python類將保存嵌入式實例的一個T C++對象,並使用嵌入式實例爲Python類和類型T註冊到Python和from-Python轉換器。
  • 明確地經由boost::python::converter::registry::push_back()

測試兌換和構造物體的步驟發生在兩個不同的步驟。由於沒有從Python轉換器註冊爲boost::function<void(int)>,Boost.Python會引發ArgumentError異常。 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)>顯式註冊來自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)>() 
    ; 
} 
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); 
} 
+0

不幸的是在這種情況下,'http'對象是全球訪問,基本上是一個單身,所以製作的包裝將無法正常工作,並我想避免使用Python實現細節來污染類。 –

+1

@ColinBasnett我最近的更新應該解決你提到的問題。 – doqtor

+0

我的天,它的作品。謝謝! –