[C++-sig] more examples of automatic type conversion
Albert Strasheim
fullung at gmail.com
Sun May 6 20:44:55 CEST 2007
Hello all
On Thu, 03 May 2007, Roman Yakovenko wrote:
> On 5/3/07, Albert Strasheim <fullung at gmail.com> wrote:
<snip>
> >Anyway, I've more or less got pass-by-value and return-by-value working
> >using Roman's example, but I can't quite figure out how to make
> >Boost.Python convert pointer and reference arguments or return values
> >into PyObject*'s using the same scheme. Should this just work if I have
> >my automatic conversion set up correctly, or is there additional magic
> >that needs to be added? Again, it would be *very* useful if copying
> >could be avoided here.
>
>
> Post small example, may be I will be able to help
To recap, the idea is to wrap classes with member functions that take
uBLAS vectors or matrices by value, (const) reference and (const)
pointer.
However, on the Python side one always wants to pass in a NumPy array
which should be converted on the fly. Using an appropriate array
adaptor, one should be able to construct a uBLAS matrix that reused the
NumPy array's underlying data buffer without copying (except in special
cases which I won't go into now).
Similarly, for (const) pointer or (const) reference returns, one wants
to wrap a new NumPy array around the return value so that the Python
side always sees a NumPy array. Again, copying should be avoided.
For the return_internal_reference case, it would be nice if the object
"owning" the matrix being returned could be kept alive until all NumPy
arrays that refer to the data are deleted.
In the manage_new_object case, the array_adaptor in the array being
returned can be told to give up the ownership of the data and then
uBLAS matrix can be deallocated immediately (effectively only
destroying a few bits and pieces).
I expect some complications here if you have a NumPy array taking over
ownership of a data buffer allocated in C++ code, so I guess one would
have to look at an array_adaptor that allocates the matrix's storage as
a NumPy array which can be "extracted" when returning to Python.
Here's some code that shows what I'm trying to using tuples instead of
NumPy arrays and array_t instead of uBLAS matrices.
#include <boost/python.hpp>
#include <iostream>
namespace py = boost::python;
struct array_t {
int rows;
int cols;
void* data;
array_t(int rows_, int cols_) : rows(rows_), cols(cols_), data(0){}
array_t() : rows(0), cols(0), data(0){}
};
struct array_converter {
static void register_to_and_from_python() {
register_from_python();
register_to_python();
}
static void register_to_python() {
py::to_python_converter<array_t, array_converter>();
}
static void register_from_python() {
py::converter::registry::push_back(
&array_converter::convertible,
&array_converter::construct,
py::type_id<array_t>());
}
static void* convertible(PyObject* obj) {
return PyTuple_Check(obj) != 0 ? obj : 0;
}
static void construct(PyObject* obj, py::converter::rvalue_from_python_stage1_data* data) {
// get the storage
typedef py::converter::rvalue_from_python_storage<array_t> storage_t;
storage_t* the_storage = reinterpret_cast<storage_t*>(data);
void* memory_chunk = the_storage->storage.bytes;
// placement new
int rows = PyInt_AsLong(PyTuple_GET_ITEM(obj, 0));
int cols = PyInt_AsLong(PyTuple_GET_ITEM(obj, 1));
// XXX get data pointer here
array_t* a = new (memory_chunk) array_t(rows, cols);
data->convertible = memory_chunk;
}
static PyObject* convert(const array_t& a) {
return py::incref(py::make_tuple(a.rows, a.cols).ptr());
}
};
struct array_test {
array_test() : data_(3, 4){}
array_t value_return() const {
return data_;
}
array_t const* const_pointer_return() const {
return &data_;
}
array_t* pointer_return() {
return &data_;
}
array_t const& const_ref_return() const {
return data_;
}
array_t& ref_return() {
return data_;
}
void value_arg(array_t a) {
print(a);
}
void pointer_arg(array_t* a) {
print(*a);
}
void const_pointer_arg(array_t const* a){}
void ref_arg(array_t& a){}
void const_ref_arg(array_t const& a){}
private:
void print(array_t const& a) {
std::cout << "(" << a.rows << ", " << a.cols << ")" << std::endl;
}
array_t data_;
};
BOOST_PYTHON_MODULE(pyublas)
{
array_converter::register_to_and_from_python();
py::class_<array_test, boost::noncopyable>("array_test")
.def("value_return", &array_test::value_return)
.def("const_pointer_return", &array_test::const_pointer_return, py::return_internal_reference<>())
#if 0
.def("pointer_return", &array_test::pointer_return)
.def("const_ref_return", &array_test::const_ref_return)
.def("ref_return", &array_test::ref_return)
#endif
.def("value_arg", &array_test::value_arg)
.def("pointer_arg", &array_test::pointer_arg)
#if 0
.def("const_pointer_arg", &array_test::const_pointer_arg)
.def("ref_arg", &array_test::ref_arg)
.def("const_ref_arg", &array_test::const_ref_arg)
#endif
;
}
And the Python code
import pyublas
print pyublas
print dir(pyublas)
at = pyublas.array_test()
print at
print at.value_return()
# XXX this doesn't work yet
print at.const_pointer_return()
a = (10, 20)
at.value_arg(a)
# XXX this doesn't work yet
at.pointer_arg(a)
The call to const_pointer_return raises the following error:
Traceback (most recent call last):
File "test_pyublas.py", line 24, in ?
print at.const_pointer_return()
TypeError: No Python class registered for C++ class struct array_t
The call to pointer_arg raises the following error:
Traceback (most recent call last):
File "test_pyublas.py", line 28, in ?
at.pointer_arg(a)
Boost.Python.ArgumentError: Python argument types in
array_test.pointer_arg(array_test, tuple)
did not match C++ signature:
pointer_arg(struct array_test {lvalue}, struct array_t *)
In both cases, I would like conversions to happen from the PyObjects to
the expected C++ data structures. Is it possible to convince
Boost.Python to do this?
Thanks for any feedback.
Cheers,
Albert
More information about the Cplusplus-sig
mailing list