SWIG/python array inside structure

Posted on

Question :

SWIG/python array inside structure

I’ve got a structure defined inside header.h that looks like :

typedef struct {
....
    int      icntl[40];
    double   cntl[15];
    int      *irn, *jcn;
....

When I init an object with this structure, I have access to integers/doubles but not arrays.

>> st.icntl
<Swig Object of type 'int *' at 0x103ce37e0>
>> st.icntl[0]
Traceback (most recent call last):
  File "test_mumps.py", line 19, in <module>
    print s.icntl[0]
TypeError: 'SwigPyObject' object is not subscriptable

How to have access to the values in read/write?

Asked By: zeapo

||

Answer #1:

The easiest way to do this is to wrap your arrays inside a struct, which can then provide extra methods to meet the “subscriptable” requirements.

I’ve put together a small example. It assumes you’re using C++, but the equivalent C version is fairly trivial to construct from this, it just requires a bit of repetition.

First up, the C++ header that has the struct we want to wrap and a template that we use for wrapping fixed size arrays:

template <typename Type, size_t N>
struct wrapped_array {
  Type data[N];
};

typedef struct {
    wrapped_array<int, 40> icntl;
    wrapped_array<double, 15> cntl;
    int      *irn, *jcn;
} Test;

Our corresponding SWIG interface then looks something like:

%module test

%{
#include "test.h"
#include <exception>
%}

%include "test.h"
%include "std_except.i"

%extend wrapped_array {
  inline size_t __len__() const { return N; }

  inline const Type& __getitem__(size_t i) const throw(std::out_of_range) {
    if (i >= N || i < 0)
      throw std::out_of_range("out of bounds access");
    return self->data[i];
  }

  inline void __setitem__(size_t i, const Type& v) throw(std::out_of_range) {
    if (i >= N || i < 0)
      throw std::out_of_range("out of bounds access");
    self->data[i] = v;
  }
}

%template (intArray40) wrapped_array<int, 40>;
%template (doubleArray15) wrapped_array<double, 15>;

The trick there is that we’ve used %extend to supply __getitem__ which is what Python uses for subscript reads and __setitem__ for the writes. (We could also have supplied a __iter__ to make the type iteratable). We also gave the specific wraped_arrays we want to use unique names to make SWIG wrap them in the output.

With the supplied interface we can now do:

>>> import test
>>> foo = test.Test()
>>> foo.icntl[30] = -654321
>>> print foo.icntl[30]
-654321
>>> print foo.icntl[40]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "test.py", line 108, in __getitem__
    def __getitem__(self, *args): return _test.intArray40___getitem__(self, *args)
IndexError: out of bounds access

You might also find this approach useful/interesting as an alternative.

Answered By: Flexo

Answer #2:

I would have done this in python

ptr = int(st.icntl)
import ctypes
icntl = ctypes.c_int * 40
icntl = icntl.from_address(ptr)

print icntl[0]
icntl[0] = 1
for i in icntl:
    print i 
Answered By: Sam

Answer #3:

Have you considered using SWIG carrays?

In your header file:

typedef struct {
    int      icntl[40];
    double   cntl[15];
} some_struct_t;

Then, in your swig file:

%module example
%include "carrays.i"  
// ...
%array_class(int, intArray);
%array_class(double, doubleArray);

The Python looks like this:

icntl = example.intArray(40)
cntl = example.doubleArray(15)
for i in range(0, 40):
    icntl[i] = i
for i in range(0, 15):
    cntl[i] = i
st = example.some_struct_t()
st.icntl = icntl
st.cntl = cntl

You still can’t set the structs directly. I write python wrapper code to hide the boilerplate.

array_class only works with basic types (int, double), if you need something else (e.g. uint8_t) you need to use array_functions, which have even more boilerplate, but they work.

http://www.swig.org/Doc3.0/SWIGDocumentation.html#Library_carrays

Answered By: Jonathan

Leave a Reply

Your email address will not be published.