How to wrap functions overloaded by type?

爱⌒轻易说出口 提交于 2019-12-13 04:43:42

问题


Suppose there is a class MyArray which implements an array of SomeType. It is written in C++ and wrapped into Python using boost::python.

BOOST_PYTHON_MODULE(my_array_module)
{
    class_<MyArray>("MyArray")
    // a few '.def's ...
    .def("__getitem__", ???)
    ;
}

The __getitem__ function in Python can either take an index and return SomeType value, or take a slice object and return slice.

There is how to deal with functions overloaded in C++ to wrap them into different Python functions. There is how to make an overloaded function in Python, if overloading means different number of args.

But how to wrap overloaded functions if they differ by arument types? I need 2 getitem functions in C++.

const SomeType& getitem(PyObject *self, size_t pos) {
    // ...
}

MyArray getitem(PyObject *self, slice sl) {
    // ...
}

If you try to wrap it using BOOST_PYTHON_FUNCTION_OVERLOADS way described here, it won't compile.

I could make a function

PyObject* getitem(PyObject *self, PyObject *pos_or_slice) {
    extract<size_t> get_pos(pos_or_slice);
    if (get_pos.check()) {
        // return a single item (SomeType)
    }
    else {
        // return a slice (MyArray)
    }
}

but I have no idea how to properly wrap MyArray into PyObject*, such that it would be consistent with the wrapping generated by class_.


回答1:


In short, as long as the C++ functions have different parameter types, then each function can be exposed as the same Python function with separate calls to def(). Boost.Python will handle the dispatching based on type conversions. If the types are ambiguous, then one often needs to create and expose an auxiliary function that manually handles dispatching based on inspecting the boost::python::object arguments.


Here is a complete minimal example demonstrating accessing a mockup Counter class's data via either an index or a slice:

#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 index access for counter object.
int spam_getitem_index(const counter& self, int index)
{
  // Attempt to convert to positive index.
  if (index < 0)
  {
    index += self.data.size();
  }

  // Check for valid range.
  if (index < 0 || self.data.size() <= index)
  {
      throw std::out_of_range("counter index out of range");
  }

  return self.data[index];
}

/// @brief Handle slicing for counter object.
boost::python::list spam_getitem_slice(
  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_slice)
    .def("__getitem__", &spam_getitem_index)
    ;
}

Interactive usage:

>>> 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:] == [])
>>> assert(counter[1]    == 1)
>>> assert(counter[-1]   == 4)
>>> counter[100]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: counter index out of range
>>> counter[-100]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: counter index out of range

Note how when spam_getitem_index() throws a std::out_of_range exception, Boost.Python translates it the associated IndexError Python exception.



来源:https://stackoverflow.com/questions/26200998/how-to-wrap-functions-overloaded-by-type

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!