【问题标题】:Boost::Python, converting tuple to Python works, vector<tuple> does notBoost::Python,将元组转换为 Python 有效,vector<tuple> 无效
【发布时间】:2017-06-30 10:20:36
【问题描述】:

我使用 Boost::Python 已经有一段时间了,结果一切正常。但是昨天我试图找出为什么我认为我已经注册的特定类型(元组)在我尝试从 Python 访问它时给了我错误。

事实证明,虽然元组实际已注册,但当尝试通过 std::vector 包装的 vector_indexing_suite 访问它时,这已经不够了。

我想知道,为什么它不起作用?有什么办法可以使这项工作?我应该尝试手动包装矢量吗?

下面是我的 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_suiteNoProxy 参数设置为 true 一起使用,则不会发生错误。但是,如果这不是必需的,我更愿意这样做,因为这会使导出的类在 Python 中不直观。

【问题讨论】:

  • 不应该 Tuples makeTuples() { return Tuples{MyTuple()}; } 改为 Tuples makeTuples() { return Tuples(); } 吗?
  • @fedepad 我这样做是为了让您可以在不触发 out_of_bounds 错误的情况下调用 makeTuples()[0],因为那时向量将为空并且您不会看到元组错误。

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


【解决方案1】:

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 已经通过引用处理任意类。为什么元组有什么不同?
  • 当然,如果你导出你的类的话。请注意如何使用class_&lt;std::vector&lt;MyTuple&gt;&gt;{"Tuples"} 导出矢量。
  • 所以基本上转换函数只有在您需要按值传递某些东西时才有用,对吧?必须创建一个采用元组类型的模板类并使用与本机 Python 元组相同的接口注册 class_,然后它才能工作。对吗?
  • 是的,这似乎是正确的。我不太确定如何为 std::tuple 正确注册 __len____getitem__,但如果你管理它,它看起来像是要走的路。
  • @Svalorzen 我用一些工作代码更新了答案。
猜你喜欢
  • 1970-01-01
  • 2021-05-29
  • 2015-01-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-11-27
  • 1970-01-01
相关资源
最近更新 更多