如何在 boost::python 中处理 Python 切片对象?

wrw*_*rwt 0 c++ python slice boost-python python-3.x

假设C++中有一个类MyArray。它实现了一个数组SomeType为了__getitem__在Python中为其创建一个函数,我做了这样的事情

const SomeType& getitem(const MyArray *arr, PyObject *slice) {
    // ???
}

BOOST_PYTHON_MODULE(mymodule) 
{
    class_<MyArray>("MyArray")
    .def("__getitem__", &getitem)
    // probably some other methods...
    ;
}
Run Code Online (Sandbox Code Playgroud)

slice使用这些函数可以获取索引。然而,“ Boost::Python 的设计理念是用户永远不会接触 PyObject* ”。

有没有更好的“提升方式”来做到这一点?

Tan*_*ury 5

Boost.Python 旨在最大限度地减少与 交互的需要PyObject,它通常通过以下方式实现这一点:

  • 提供更高级别的类型包装器。
  • 允许通过关联的boost::python::object.

例如,可以通过 C++ 访问 Python 对象的接口,其方式与在 Python 中的方式类似。下面演示了访问引用 Python实例start的 a 的属性:boost::python::objectslice

namespace python = boost::python;
python::object slice = get_slice_object();
python::object start = slice.attr("start");
std::size_t start_index = !start.is_none()
  ? python::extract<std::size_t>(start) // Extract index.
  : 0;                                  // Default.
Run Code Online (Sandbox Code Playgroud)

虽然这种方法有效,但它往往会产生大量样板代码:在None提供时创建默认值、处理零长度切片以及将负索引转换为正索引。在这种情况下,Boost.Python 提供了一个更高级别的类型包装器boost::python::slice,它具有一个get_indices()成员函数,可以删除大部分样板代码。这是一个完整的最小示例:

#include <vector>
#include <boost/range/algorithm.hpp>
#include <boost/range/irange.hpp>
#include <boost/python.hpp>
#include <boost/python/slice.hpp>

/// @brief Mockup class that creates a range from 0 to N.
struct counter
{
  counter(std::size_t n)
  {
    data.reserve(n);
    boost::copy(boost::irange(std::size_t(0), n), std::back_inserter(data));
  }

  std::vector<int> data;
};

/// @brief Handle slicing for counter object.
boost::python::list spam_getitem(
  const counter& self,
  boost::python::slice slice)
{
  namespace python = boost::python;
  python::list result;

  // Boost.Python will throw std::invalid_argument if the range would be
  // empty.
  python::slice::range<std::vector<int>::const_iterator> range;
  try
  {
    range = slice.get_indices(self.data.begin(), self.data.end());
  }
  catch (std::invalid_argument)
  {
    return result;
  }

  // Iterate over fully-closed range.
  for (; range.start != range.stop; std::advance(range.start, range.step))
  {
    result.append(*range.start);
  }
  result.append(*range.start); // Handle last item.
  return result;
}

BOOST_PYTHON_MODULE(example)
{
  namespace python = boost::python;
  python::class_<counter>("Counter", python::init<int>())
    .def("__getitem__", &spam_getitem)
    ;
}
Run Code Online (Sandbox Code Playgroud)

互动使用:

>>> from example import Counter
>>> counter = Counter(5)
>>> assert(counter[:]    == [0,1,2,3,4])
>>> assert(counter[:-2]  == [0,1,2])
>>> assert(counter[-2:]  == [3,4])
>>> assert(counter[::2]  == [0,2,4])
>>> assert(counter[1::2] == [1,3])
>>> assert(counter[100:] == [])
Run Code Online (Sandbox Code Playgroud)