我有一个struct
,其中包含一个C风格的数组数据成员。我想将这个结构体暴露给Python,并且这个数据成员可以作为Python中的列表访问。
struct S
{
char arr[4128];
};
void foo( S const * )
{}
BOOST_PYTHON_MODULE( test )
{
using namespace boost::python;
class_<S>( "S" )
.def_readwrite( "arr", &S::arr )
;
def( "foo", foo );
}
上面的代码无法构建
error C2440: '=' : cannot convert from 'const char [4128]' to 'char [4128]'
C风格的数组不可分配,因此错误很有意义。如果我将数据成员更改为普通char
而不是数组,则代码将编译。
我无法用std::array
或其他容器替换数组,因为C API正在使用该结构。我能想到的唯一解决方案是编写几个包装器并执行以下操作
struct S1
重复S
,除了它有std::array
而不是C风格的数组foo_wrapper
的{{1}},将内容复制到S1 const *
个实例并调用S
foo
以将to_python_converter
转换为Python列表这应该可行,而且我现在并不太关心数据复制,但如果我可以避免它并直接公开数组而不必跳过所有这些环节,那就太好了。
所以问题是,如何将这个C风格的数组成员公开给Python作为列表?
答案 0 :(得分:7)
正如您所见,Boost.Python遗憾的是没有为C数组提供自动转换器,甚至它提供的STL容器包装也不是我建议接近它的方式(至少如果您的真实问题与您的示例类似,就数据的大小而言,如果您知道您希望将其作为真正的Python元组公开)。
我建议编写一个函数,将C数组转换为Python元组,直接使用Python C-API,或者使用boost :: python包装器,然后通过属性公开数据成员。虽然你可以通过使用NumPy数组而不是元组来避免数据复制,但是对于不值得努力的小数组来说。
例如:
namespace bp = boost::python;
bp::tuple wrap_arr(S const * s) {
bp::list a;
for (int i = 0; i < 10; ++i) {
a.append(s->arr[i]);
}
return bp::tuple(a);
}
BOOST_PYTHON_MODULE( test ) {
bp::class_<S>( "S" )
.add_property("arr", wrap_arr)
;
}
答案 1 :(得分:6)
我遇到的解决方案是创建一个类array_ref
,它为C风格的数组提供非拥有的视图。从array_indexing_suite
复制粘贴的另一个类boost::python::vector_indexing_suite
,其中包含所有成员函数,这些函数改变了从原始文件修改的数组的大小以抛出错误,然后用于将array_ref
包装起来允许Python的索引操作。相关代码如下:
<强> array_ref
强>
#include <cstddef>
#include <iterator>
#include <stdexcept>
/**
* array_ref offers a non-const view into an array. The storage for the array is not owned by the
* array_ref object, and it is the client's responsibility to ensure the backing store reamins
* alive while the array_ref object is in use.
*
* @tparam T
* Type of elements in the array
*/
template<typename T>
class array_ref
{
public:
/** Alias for the type of elements in the array */
typedef T value_type;
/** Alias for a pointer to value_type */
typedef T *pointer;
/** Alias for a constant pointer to value_type */
typedef T const *const_pointer;
/** Alias for a reference to value_type */
typedef T& reference;
/** Alias for a constant reference to value_type */
typedef T const& const_reference;
/** Alias for an iterator pointing at value_type objects */
typedef T *iterator;
/** Alias for a constant iterator pointing at value_type objects */
typedef T const *const_iterator;
/** Alias for a reverse iterator pointing at value_type objects */
typedef std::reverse_iterator<iterator> reverse_iterator;
/** Alias for a constant reverse iterator pointing at value_type objects */
typedef std::reverse_iterator<const_iterator> const_reverse_iterator;
/** Alias for an unsigned integral type used to represent size related values */
typedef std::size_t size_type;
/** Alias for a signed integral type used to represent result of difference computations */
typedef std::ptrdiff_t difference_type;
/** Default constructor */
constexpr array_ref() noexcept = default;
/**
* Constructor that accepts a pointer to an array and the number of elements pointed at
*
* @param arr
* Pointer to array
* @param length
* Number of elements pointed at
*/
constexpr array_ref( pointer arr, size_type length ) noexcept
: begin_(arr)
, length_(length)
{}
/**
* Constructor that accepts a reference to an array
*
* @tparam N
* Number of elements in the array
*/
template<size_type N>
constexpr array_ref( T (&arr)[N] ) noexcept
: begin_(&arr[0])
, length_(N)
{}
/**
* Constructor taking a pair of pointers pointing to the first element and one past the last
* element of the array, respectively.
*
* @param first
* Pointer to the first element of the array
* @param last
* Pointer to one past the last element of the array
*/
array_ref( pointer first, pointer last ) noexcept
: begin_(first)
, length_(static_cast<size_type>(std::distance(first, last)))
{}
/** Copy constructor */
constexpr array_ref( array_ref const& ) noexcept = default;
/** Copy assignment operator */
array_ref& operator=( array_ref const& ) noexcept = default;
/** Move constructor */
constexpr array_ref( array_ref&& ) noexcept = default;
/** Move assignment operator */
array_ref& operator=( array_ref&& ) noexcept = default;
/**
* Returns an iterator to the first element of the array. If the array is empty, the
* returned iterator will be equal to end().
*
* @return An iterator to the first element of the array
*/
/*constexpr*/ iterator begin() noexcept
{
return begin_;
}
/**
* Returns a constant iterator to the first element of the array. If the array is empty, the
* returned iterator will be equal to end().
*
* @return A constant iterator to the first element of the array
*/
constexpr const_iterator begin() const noexcept
{
return begin_;
}
/**
* Returns a constant iterator to the first element of the array. If the array is empty, the
* returned iterator will be equal to end().
*
* @return A constant iterator to the first element of the array
*/
constexpr const_iterator cbegin() const noexcept
{
return begin_;
}
/**
* Returns an iterator to the element following the last element of the array.
*
* @return An iterator to the element following the last element of the array
*/
/*constexpr*/ iterator end() noexcept
{
return begin() + size();
}
/**
* Returns a constant iterator to the element following the last element of the array.
*
* @return A constant iterator to the element following the last element of the array
*/
constexpr const_iterator end() const noexcept
{
return begin() + size();
}
/**
* Returns a constant iterator to the element following the last element of the array.
*
* @return A constant iterator to the element following the last element of the array
*/
constexpr const_iterator cend() const noexcept
{
return cbegin() + size();
}
/**
* Returns a reverse iterator to the first element of the reversed array. It corresponds to the
* last element of the non-reversed array.
*
* @return A reverse iterator to the first element of the reversed array
*/
reverse_iterator rbegin() noexcept
{
return reverse_iterator( end() );
}
/**
* Returns a constant reverse iterator to the first element of the reversed array. It corresponds
* to the last element of the non-reversed array.
*
* @return A constant reverse iterator to the first element of the reversed array
*/
const_reverse_iterator rbegin() const noexcept
{
return const_reverse_iterator( cend() );
}
/**
* Returns a constant reverse iterator to the first element of the reversed array. It corresponds
* to the last element of the non-reversed array.
*
* @return A constant reverse iterator to the first element of the reversed array
*/
const_reverse_iterator crbegin() const noexcept
{
return const_reverse_iterator( cend() );
}
/**
* Returns a reverse iterator to the element following the last element of the reversed array. It
* corresponds to the element preceding the first element of the non-reversed array.
*
* @return A reverse iterator to the element following the last element of the reversed array
*/
reverse_iterator rend() noexcept
{
return reverse_iterator( begin() );
}
/**
* Returns a constant reverse iterator to the element following the last element of the reversed
* array. It corresponds to the element preceding the first element of the non-reversed array.
*
* @return A constant reverse iterator to the element following the last element of the reversed
* array
*/
const_reverse_iterator rend() const noexcept
{
return const_reverse_iterator( cbegin() );
}
/**
* Returns a constant reverse iterator to the element following the last element of the reversed
* array. It corresponds to the element preceding the first element of the non-reversed array.
*
* @return A constant reverse iterator to the element following the last element of the reversed
* array
*/
const_reverse_iterator crend() const noexcept
{
return const_reverse_iterator( cbegin() );
}
/**
* Returns the number of elements in the array.
*
* @return The number of elements in the array
*/
constexpr size_type size() const noexcept
{
return length_;
}
/**
* Indicates whether the array has no elements
*
* @return true if the array has no elements, false otherwise
*/
constexpr bool empty() const noexcept
{
return size() == 0;
}
/**
* Returns a reference to the element at the specified location.
*
* @return A reference to the element at the specified location
* @pre i < size()
*/
/*constexpr*/ reference operator[]( size_type i )
{
#ifndef NDEBUG
return at( i );
#else
return *(begin() + i);
#endif
}
/**
* Returns a constant reference to the element at the specified location.
*
* @return A constant reference to the element at the specified location
* @pre i < size()
*/
constexpr const_reference operator[]( size_type i ) const
{
#ifndef NDEBUG
return at( i );
#else
return *(begin() + i);
#endif
}
/**
* Returns a reference to the element at the specified location, with bounds checking.
*
* @return A reference to the element at the specified location
* @throw std::out_of_range if the specified index is not within the range of the array
*/
/*constexpr*/ reference at( size_type i )
{
if( i >= size() ) {
throw std::out_of_range( "index out of range" );
}
return *(begin() + i);
}
/**
* Returns a constant reference to the element at the specified location, with bounds checking.
*
* @return A constant reference to the element at the specified location
* @throw std::out_of_range if the specified index is not within the range of the array
*/
/*constexpr*/ const_reference at( size_type i ) const
{
if( i >= size() ) {
throw std::out_of_range( "index out of range" );
}
return *(begin() + i);
}
/**
* Returns a reference to the first element of the array
*
* @return A reference to the first element of the array
* @pre empty() == false
*/
/*constexpr*/ reference front() noexcept
{
return *begin();
}
/**
* Returns a reference to the first element of the array
*
* @return A reference to the first element of the array
* @pre empty() == false
*/
constexpr const_reference front() const noexcept
{
return *begin();
}
/**
* Returns a reference to the last element of the array
*
* @return A reference to the last element of the array
* @pre empty() == false
*/
/*constexpr*/ reference back() noexcept
{
return *(end() - 1);
}
/**
* Returns a constant reference to the last element of the array
*
* @return A constant reference to the last element of the array
* @pre empty() == false
*/
constexpr const_reference back() const noexcept
{
return *(end() - 1);
}
/**
* Returns a pointer to the address of the first element of the array
*
* @return A pointer to the address of the first element of the array
*/
/*constexpr*/ pointer data() noexcept
{
return begin();
}
/**
* Returns a constant pointer to the address of the first element of the array
*
* @return A constant pointer to the address of the first element of the array
*/
constexpr const_pointer data() const noexcept
{
return begin();
}
/**
* Resets the operand back to its default constructed state
*
* @post empty() == true
*/
void clear() noexcept
{
begin_ = nullptr;
length_ = 0;
}
private:
/** Pointer to the first element of the referenced array */
pointer begin_ = nullptr;
/** Number of elements in the referenced array */
size_type length_ = size_type();
};
<强> array_indexing_suite
强>
#include <boost/python.hpp>
#include <boost/python/suite/indexing/indexing_suite.hpp>
#include <algorithm>
#include <cstddef>
#include <iterator>
#include <type_traits>
// Forward declaration
template<
typename Array,
bool NoProxy,
typename DerivedPolicies>
class array_indexing_suite;
namespace detail {
template<typename Array, bool NoProxy>
struct final_array_derived_policies
: array_indexing_suite<Array, NoProxy, final_array_derived_policies<Array, NoProxy>>
{};
} /* namespace detail */
template<
typename Array,
bool NoProxy = std::is_arithmetic<typename Array::value_type>::value,
typename DerivedPolicies = detail::final_array_derived_policies<Array, NoProxy>
>
class array_indexing_suite
: public boost::python::indexing_suite<Array,
DerivedPolicies,
NoProxy>
{
public:
typedef typename Array::value_type data_type;
typedef typename Array::value_type key_type;
typedef typename Array::size_type index_type;
typedef typename Array::size_type size_type;
typedef typename Array::difference_type difference_type;
static data_type& get_item( Array& arr, index_type i )
{
return arr[i];
}
static void set_item( Array& arr, index_type i, data_type const& v )
{
arr[i] = v;
}
static void delete_item( Array& /*arr*/, index_type /*i*/ )
{
::PyErr_SetString( ::PyExc_TypeError, "Cannot delete array item" );
boost::python::throw_error_already_set();
}
static size_type size( Array& arr )
{
return arr.size();
}
static bool contains( Array& arr, key_type const& key )
{
return std::find( arr.cbegin(), arr.cend(), key ) != arr.cend();
}
static index_type get_min_index( Array& )
{
return 0;
}
static index_type get_max_index( Array& arr )
{
return arr.size();
}
static bool compare_index( Array&, index_type a, index_type b )
{
return a < b;
}
static index_type convert_index( Array& arr, PyObject *i_ )
{
boost::python::extract<long> i(i_);
if( i.check() ) {
long index = i();
if( index < 0 ) {
index += static_cast<decltype(index)>(DerivedPolicies::size( arr ));
}
if( ( index >= long(arr.size()) ) || ( index < 0 ) ) {
::PyErr_SetString( ::PyExc_IndexError, "Index out of range" );
boost::python::throw_error_already_set();
}
return index;
}
::PyErr_SetString( ::PyExc_TypeError, "Invalid index type" );
boost::python::throw_error_already_set();
return index_type();
}
static boost::python::object get_slice( Array& arr, index_type from, index_type to )
{
if( from > to ) {
return boost::python::object( Array() );
}
return boost::python::object( Array( arr.begin() + from, arr.begin() + to ) );
}
static void set_slice( Array& arr, index_type from, index_type to, data_type const& v )
{
if( from > to ) {
return;
} else if( to > arr.size() ) {
::PyErr_SetString( ::PyExc_IndexError, "Index out of range" );
boost::python::throw_error_already_set();
} else {
std::fill( arr.begin() + from, arr.begin() + to, v );
}
}
template<typename Iter>
static void set_slice( Array& arr, index_type from, index_type to, Iter first, Iter last )
{
auto num_items = std::distance( first, last );
if( ( from + num_items ) > arr.size() ) {
::PyErr_SetString( ::PyExc_IndexError, "Index out of range" );
boost::python::throw_error_already_set();
return;
}
if( from > to ) {
std::copy( first, last, arr.begin() + from );
} else {
if( static_cast<decltype(num_items)>( to - from ) != num_items ) {
::PyErr_SetString( ::PyExc_TypeError, "Array length is immutable" );
boost::python::throw_error_already_set();
return;
}
std::copy( first, last, arr.begin() + from );
}
}
static void delete_slice( Array& /*arr*/, index_type /*from*/, index_type /*to*/ )
{
::PyErr_SetString( ::PyExc_TypeError, "Cannot delete array item(s)" );
boost::python::throw_error_already_set();
}
};
最后,创建绑定所需的位。
struct foo
{
char data[100];
};
BOOST_PYTHON_MODULE( foo_module )
{
using namespace boost::python;
class_<array_ref<unsigned char>>( "uchar_array" )
.def( array_indexing_suite<array_ref<unsigned char>>() )
;
class_<foo>( "foo", "Foo's description" )
.add_property( "data",
/* getter that returns an array_ref view into the array */
static_cast<array_ref<unsigned char>(*)( foo * )>(
[]( foo *obj ) {
return array_ref<unsigned char>( obj->data );
}),
"Array of data bytes" )
;
}