问题
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