Boost::Python,将元组转换为 Python 有效,向量 <tuple> 无效
Boost::Python, converting tuple to Python works, vector<tuple> does not
我已经使用 Boost::Python 一段时间了,结果总是一切正常。然而昨天我试图找出为什么我认为我已经注册的特定类型(一个元组)在我试图从 Python.
访问它时给我错误
事实证明,虽然元组实际上已注册,但当尝试通过 vector_indexing_suite
包裹的 std::vector
访问它时,这已经不够了。
我想知道,为什么它不起作用?有什么办法可以使这项工作?我应该尝试手动包装向量吗?
下面是我的 MVE:
#include <tuple>
#include <vector>
#include <boost/python.hpp>
#include <boost/python/suite/indexing/vector_indexing_suite.hpp>
template <typename T>
struct TupleToPython {
TupleToPython() {
boost::python::to_python_converter<T, TupleToPython<T>>();
}
template<int...>
struct sequence {};
template<int N, int... S>
struct generator : generator<N-1, N-1, S...> { };
template<int... S>
struct generator<0, S...> {
using type = sequence<S...>;
};
template <int... I>
static boost::python::tuple boostConvertImpl(const T& t, sequence<I...>) {
return boost::python::make_tuple(std::get<I>(t)...);
}
template <typename... Args>
static boost::python::tuple boostConvert(const std::tuple<Args...> & t) {
return boostConvertImpl(t, typename generator<sizeof...(Args)>::type());
}
static PyObject* convert(const T& t) {
return boost::python::incref(boostConvert(t).ptr());
}
};
using MyTuple = std::tuple<int>;
using Tuples = std::vector<MyTuple>;
MyTuple makeMyTuple() {
return MyTuple();
}
Tuples makeTuples() {
return Tuples{MyTuple()};
}
BOOST_PYTHON_MODULE(h)
{
using namespace boost::python;
TupleToPython<MyTuple>();
def("makeMyTuple", makeMyTuple);
class_<std::vector<MyTuple>>{"Tuples"}
.def(vector_indexing_suite<std::vector<MyTuple>>());
def("makeTuples", makeTuples);
}
通过 Python 访问结果 .so
结果:
>>> print makeMyTuple()
(0,)
>>> print makeTuples()[0]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: No Python class registered for C++ class std::tuple<int>
>>>
编辑:
我已经意识到,如果将 vector_indexing_suite
与 NoProxy
参数设置为 true 一起使用,则不会发生错误。但是,如果这不是必需的,我更愿意,因为它使导出的 类 在 Python.
中不直观
TupleToPython
注册 C++ 到 Python 转换器和 Python 到 C++ 转换器。这很好。
另一方面,您希望向量元素通过引用返回。但是 Python 端没有任何内容可以作为元组的引用。转换为 Python 的元组可能包含相同的值,但它与原始 C++ 元组完全分离。
看起来为了通过引用导出元组,需要为其创建一个索引套件,而不是 to/from-Python 转换器。我从来没有这样做过,不能保证它会起作用。
以下是如何将元组公开为类似元组的最小 Python 对象(只有 len() 和索引)。首先定义一些辅助函数:
template <typename A>
int tuple_length(const A&)
{
return std::tuple_size<A>::value;
}
template <int cidx, typename ... A>
typename std::enable_if<cidx >= sizeof...(A), boost::python::object>::type
get_tuple_item_(const std::tuple<A...>& a, int idx, void* = nullptr)
{
throw std::out_of_range{"Ur outta range buddy"};
}
template <int cidx, typename ... A, typename = std::enable_if<(cidx < sizeof ...(A))>>
typename std::enable_if<cidx < sizeof...(A), boost::python::object>::type
get_tuple_item_(const std::tuple<A...>& a, int idx, int = 42)
{
if (idx == cidx)
return boost::python::object{std::get<cidx>(a)};
else
return get_tuple_item_<cidx+1>(a, idx);
};
template <typename A>
boost::python::object get_tuple_item(const A& a, int index)
{
return get_tuple_item_<0>(a, index);
}
然后公开特定的元组:
using T1 = std::tuple<int, double, std::string>;
using T2 = std::tuple<std::string, int>;
BOOST_PYTHON_MODULE(z)
{
using namespace boost::python;
class_<T1>("T1", init<int, double, std::string>())
.def("__len__", &tuple_length<T1>)
.def("__getitem__", &get_tuple_item<T1>);
class_<T2>("T2", init<std::string, int>())
.def("__len__", &tuple_length<T2>)
.def("__getitem__", &get_tuple_item<T2>);
}
请注意,与真正的 Python 元组不同,这些准元组是可变的(通过 C++)。由于元组不变性,通过转换器和 NoProxy
导出看起来是一个可行的替代方案。
我已经使用 Boost::Python 一段时间了,结果总是一切正常。然而昨天我试图找出为什么我认为我已经注册的特定类型(一个元组)在我试图从 Python.
访问它时给我错误事实证明,虽然元组实际上已注册,但当尝试通过 vector_indexing_suite
包裹的 std::vector
访问它时,这已经不够了。
我想知道,为什么它不起作用?有什么办法可以使这项工作?我应该尝试手动包装向量吗?
下面是我的 MVE:
#include <tuple>
#include <vector>
#include <boost/python.hpp>
#include <boost/python/suite/indexing/vector_indexing_suite.hpp>
template <typename T>
struct TupleToPython {
TupleToPython() {
boost::python::to_python_converter<T, TupleToPython<T>>();
}
template<int...>
struct sequence {};
template<int N, int... S>
struct generator : generator<N-1, N-1, S...> { };
template<int... S>
struct generator<0, S...> {
using type = sequence<S...>;
};
template <int... I>
static boost::python::tuple boostConvertImpl(const T& t, sequence<I...>) {
return boost::python::make_tuple(std::get<I>(t)...);
}
template <typename... Args>
static boost::python::tuple boostConvert(const std::tuple<Args...> & t) {
return boostConvertImpl(t, typename generator<sizeof...(Args)>::type());
}
static PyObject* convert(const T& t) {
return boost::python::incref(boostConvert(t).ptr());
}
};
using MyTuple = std::tuple<int>;
using Tuples = std::vector<MyTuple>;
MyTuple makeMyTuple() {
return MyTuple();
}
Tuples makeTuples() {
return Tuples{MyTuple()};
}
BOOST_PYTHON_MODULE(h)
{
using namespace boost::python;
TupleToPython<MyTuple>();
def("makeMyTuple", makeMyTuple);
class_<std::vector<MyTuple>>{"Tuples"}
.def(vector_indexing_suite<std::vector<MyTuple>>());
def("makeTuples", makeTuples);
}
通过 Python 访问结果 .so
结果:
>>> print makeMyTuple()
(0,)
>>> print makeTuples()[0]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: No Python class registered for C++ class std::tuple<int>
>>>
编辑:
我已经意识到,如果将 vector_indexing_suite
与 NoProxy
参数设置为 true 一起使用,则不会发生错误。但是,如果这不是必需的,我更愿意,因为它使导出的 类 在 Python.
TupleToPython
注册 C++ 到 Python 转换器和 Python 到 C++ 转换器。这很好。
另一方面,您希望向量元素通过引用返回。但是 Python 端没有任何内容可以作为元组的引用。转换为 Python 的元组可能包含相同的值,但它与原始 C++ 元组完全分离。
看起来为了通过引用导出元组,需要为其创建一个索引套件,而不是 to/from-Python 转换器。我从来没有这样做过,不能保证它会起作用。
以下是如何将元组公开为类似元组的最小 Python 对象(只有 len() 和索引)。首先定义一些辅助函数:
template <typename A>
int tuple_length(const A&)
{
return std::tuple_size<A>::value;
}
template <int cidx, typename ... A>
typename std::enable_if<cidx >= sizeof...(A), boost::python::object>::type
get_tuple_item_(const std::tuple<A...>& a, int idx, void* = nullptr)
{
throw std::out_of_range{"Ur outta range buddy"};
}
template <int cidx, typename ... A, typename = std::enable_if<(cidx < sizeof ...(A))>>
typename std::enable_if<cidx < sizeof...(A), boost::python::object>::type
get_tuple_item_(const std::tuple<A...>& a, int idx, int = 42)
{
if (idx == cidx)
return boost::python::object{std::get<cidx>(a)};
else
return get_tuple_item_<cidx+1>(a, idx);
};
template <typename A>
boost::python::object get_tuple_item(const A& a, int index)
{
return get_tuple_item_<0>(a, index);
}
然后公开特定的元组:
using T1 = std::tuple<int, double, std::string>;
using T2 = std::tuple<std::string, int>;
BOOST_PYTHON_MODULE(z)
{
using namespace boost::python;
class_<T1>("T1", init<int, double, std::string>())
.def("__len__", &tuple_length<T1>)
.def("__getitem__", &get_tuple_item<T1>);
class_<T2>("T2", init<std::string, int>())
.def("__len__", &tuple_length<T2>)
.def("__getitem__", &get_tuple_item<T2>);
}
请注意,与真正的 Python 元组不同,这些准元组是可变的(通过 C++)。由于元组不变性,通过转换器和 NoProxy
导出看起来是一个可行的替代方案。