发送 Python 函数作为 Boost.Function 参数
Sending Python function as Boost.Function argument
在我尝试将 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 上问过 ,得到了一个惊人的答案。我还想知道给出的答案中的类似方法是否也可以应用于此用例。
非常感谢大家的支持!
一种解决方案是添加重载函数:
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 东西污染您的主要 class,那么您可以创建一个包装器 class。事情看起来也更干净了:
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 可调用函数转换器。这将解决单例问题并且不需要更改主要 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);
}
当调用通过 Boost.Python 公开的函数时,Boost.Python 将查询其注册表以根据以下条件为每个调用者的参数找到合适的 from-Python 转换器所需的 C++ 类型。如果找到知道如何将 Python 对象转换为 C++ 对象的转换器,那么它将使用转换器来构造 C++ 对象。如果找不到合适的转换器,则 Boost.Python 将引发 ArgumentError
异常。
from-Python 转换器已注册:
- 自动为 Boost.Python 支持的类型,例如
int
和 std::string
- 隐式用于
boost::python::class<T>
公开的类型。默认情况下,生成的 Python class 将保存一个 T
C++ 对象的嵌入式实例,并注册 to-Python 和 from-Python 转换器Python class 并键入 T
,使用嵌入式实例。
- 明确地通过
boost::python::converter::registry::push_back()
测试可转换性和构造对象的步骤分为两个不同的步骤。由于没有为 boost::function<void(int)>
注册来自-Python 的转换器,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 公开的 all 函数可以使用转换器(例如,不需要为每个函数编写垫片)。但是,需要为每个 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 代码与我的 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 上问过
非常感谢大家的支持!
一种解决方案是添加重载函数:
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 东西污染您的主要 class,那么您可以创建一个包装器 class。事情看起来也更干净了:
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 可调用函数转换器。这将解决单例问题并且不需要更改主要 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);
}
当调用通过 Boost.Python 公开的函数时,Boost.Python 将查询其注册表以根据以下条件为每个调用者的参数找到合适的 from-Python 转换器所需的 C++ 类型。如果找到知道如何将 Python 对象转换为 C++ 对象的转换器,那么它将使用转换器来构造 C++ 对象。如果找不到合适的转换器,则 Boost.Python 将引发 ArgumentError
异常。
from-Python 转换器已注册:
- 自动为 Boost.Python 支持的类型,例如
int
和std::string
- 隐式用于
boost::python::class<T>
公开的类型。默认情况下,生成的 Python class 将保存一个T
C++ 对象的嵌入式实例,并注册 to-Python 和 from-Python 转换器Python class 并键入T
,使用嵌入式实例。 - 明确地通过
boost::python::converter::registry::push_back()
测试可转换性和构造对象的步骤分为两个不同的步骤。由于没有为 boost::function<void(int)>
注册来自-Python 的转换器,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 公开的 all 函数可以使用转换器(例如,不需要为每个函数编写垫片)。但是,需要为每个 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)>()
;
}