[英]Sending Python function as Boost.Function argument
Things are getting complicated in my world of trying to mesh Python code with my C++. 在试图将Python代码与C ++结合在一起的世界中,事情变得越来越复杂。
Essentially, I want to be able to assign a callback function to be used after an HTTP call receives a response, and I want to be able to do this from either C++ or Python. 本质上,我希望能够分配一个在HTTP调用收到响应后使用的回调函数,并且我希望能够从C ++或Python做到这一点。
In other words, I want to be able to call this from C++: 换句话说,我希望能够从C ++调用此函数:
http.get_asyc("www.google.ca", [&](int a) { std::cout << "response recieved: " << a << std::endl; });
and this from Python: 而这来自Python:
def f(r):
print str.format('response recieved: {}', r)
http.get_async('www.google.ca', f)
I have set up a demo on Coliru that shows exactly what I'm trying to accomplish. 我已经在Coliru上建立了一个演示,该演示准确地显示了我要完成的工作。 Here is the code and the error that I am getting:
这是代码和我得到的错误:
#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);
}
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)>)
I'm not sure why the function
is not being converted to a boost::function
automatically. 我不确定为什么该
function
没有自动转换为boost::function
。
I have asked a vaguely similar question on SO before and got an amazing answer. 我之前在SO上也提出过类似的疑问 ,并且得到了一个了不起的答案。 I also wonder if a similar method in the answer given there could be applied to this use-case as well.
我也想知道是否也可以将类似的方法应用于此用例。
Thank you very much for any and all support! 非常感谢您的支持!
When a function that has been exposed through Boost.Python is invoked, Boost.Python will query its registry to locate a suitable from-Python converter for each of the caller's arguments based on the desired C++ type. 调用通过Boost.Python公开的函数时,Boost.Python将查询其注册表,以根据所需的C ++类型为调用者的每个参数找到一个合适的from-Python转换器。 If a converter is found that knows how to convert from the Python object to the C++ object, then it will use the converter to construct the C++ object.
如果找到了知道如何从Python对象转换为C ++对象的转换器,则它将使用该转换器构造C ++对象。 If no suitable converters are found, then Boost.Python will raise an
ArgumentError
exception. 如果找不到合适的转换器,则Boost.Python将引发
ArgumentError
异常。
The from-Python converters are registered: Python源转换器已注册:
int
and std::string
int
和std::string
boost::python::class<T>
. boost::python::class<T>
公开的类型隐式表示。 By default, the resulting Python class will hold an embedded instance of a T
C++ object, and register to-Python and from-Python converters for the Python class and type T
, using the embedded instance. T
C ++对象的嵌入式实例,并使用该嵌入式实例为Python类和类型T
注册到Python和从Python的转换器。 boost::python::converter::registry::push_back()
boost::python::converter::registry::push_back()
显式地 The steps of testing for convertibility and constructing an object occur in two distinct steps. 测试可转换性和构造对象的步骤分两个不同的步骤进行。 As no from-Python converter has been registered for
boost::function<void(int)>
, Boost.Python will raise an ArgumentError
exception. 由于没有为
boost::function<void(int)>
注册来自Python的转换器,因此Boost.Python将引发ArgumentError
异常。 Boost.Python will not attempt construct the boost::function<void(int)>
object, despite boost::function<void(int)>
being constructible from a boost::python::object
. 尽管
boost::function<void(int)>
从boost::python::object
构造,但Boost.Python不会尝试构造boost::function<void(int)>
boost::python::object
。
To resolve this, consider using an shim function to defer the construction of boost::function<void(int)>
until after the boost::python::object
has passed through the Boost.Python layer: 若要解决此问题,请考虑使用shim函数将
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);
...
}
Here is a complete example demonstrating this approach: 这是演示此方法的完整示例:
#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);
}
Interactive usage: 互动用法:
>>> 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
...
An alternative approach is to explicitly register a from-Python converter for boost::function<void(int)>
. 另一种方法是显式注册Python转换器的
boost::function<void(int)>
。 This has the benefit that all functions exposed through Boost.Python can use the converter (eg one would not need to write a shim for each function). 这样的好处是,通过Boost.Python公开的所有函数都可以使用转换器(例如,无需为每个函数编写垫片)。 However, a convert would need to be registered for each C++ type.
但是,将需要为每种C ++类型注册一个转换。 Here is an example demonstrating explicitly registering a custom converter for
boost::function<void(int)>
and boost::function<void(std::string)>
: 这是一个示例, 展示了为
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)>()
;
}
One solution is to add an overload function: 一种解决方案是添加重载函数:
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));
}
Then expose just this specific overload: 然后公开此特定的重载:
.def("get_async", static_cast<void (http_manager::*)(std::string, boost::python::object)>(&http_manager::get_async))
Or if your don't want to pollute your main class with python stuff then you could create a wrapper class. 或者,如果您不想用python的东西污染您的主类,则可以创建一个包装器类。 Things look much cleaner then too:
事情看起来也更加干净:
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);
}
Update: Another option is to use a python callable to boost function converter. 更新:另一个选择是使用可调用的python来增强函数转换器。 This will address the singleton problem and won't require changes to the main class.
这将解决单例问题,并且不需要更改主类。
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);
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.