I have written a Python extension for a C library. I have a data structure that looks like this:
typedef struct _mystruct{
double * clientdata;
size_t len;
} MyStruct;
The purpose of this datatype maps directly to the list data type in Python. I therefore, want to create ‘list-like’ behavior for the exported struct, so that code written using my C extension is more ‘Pythonic’.
In particular, this is what I want to be able to do (from python code)
Note: py_ctsruct is a ctsruct datatype being accessed in python.
My requirements can be sumarized as:
- list(py_ctsruct) returns a python list with all contents copied out from the c struct
- py_cstruct[i] returns ith element (preferably throws IndexError on invalid index)
- for elem in py_ctsruct: ability to enumerate
According to PEP234, An object can be iterated over with “for” if it implements
_iter_() or _getitem_(). Using that logic then, I think that by adding the following attributes (via rename) to my SWIG interface file, I will have the desired behavior (apart from req. #1 above – which I still dont know how to achieve):
__len__
__getitem__
__setitem__
I am now able to index the C object in python. I have not yet implemented the Python exception throwing, however if array bounds are exceeded, are return a magic number (error code).
The interesting thing is that when I attempt to iterate over the struct using ‘for x in’ syntax for example:
for i in py_cstruct:
print i
Python enters into an infinite loop that simply prints the magic (error) number mentioned above, on the console. which suggests to me that there is something wrong with the indexing.
last but not the least, how can I implement requirement 1? this involves (as I understand it):
- handling’ the function call list() from python
- Returning a Python (list) data type from C code
[[Update]]
I would be interested in seeing a little code snippet on what (if any) declarations I need to put in my interface file, so that I can iterate over the elements of the c struct, from Python.
The simplest solution to this is to implement
__getitem__and throw anIndexErrorexception for an invalid index.I put together an example of this, using
%extendand%exceptionin SWIG to implement__getitem__and raise an exception respectively:I tested it by adding to test.h:
And running the following Python:
Which prints:
and then finishes.
An alternative approach, using a typemap to map
MyStructonto aPyListdirectly is possible too:This will create a
PyListwith the return value from any function that returns aMyStruct *. I tested this%typemap(out)with the exact same function as the previous method.You can also write a corresponding
%typemap(in)and%typemap(freearg)for the reverse, something like this untested code:Using an iterator would make more sense for containers like linked lists, but for completeness sake here’s how you might go about doing it for
MyStructwith__iter__. The key bit is that you get SWIG to wrap another type for you, which provides the__iter__()andnext()needed, in this caseMyStructIterwhich is defined and wrapped at the same time using%inlinesince it’s not part of the normal C API:The requirements for iteration over containers are such that the container needs to implement
__iter__()and return a new iterator, but in addition tonext()which returns the next item and increments the iterator the iterator itself must also supply a__iter__()method. This means that either the container or an iterator can be used identically.MyStructIterneeds to keep track of the current state of iteration – where we are and how much we have left. In this example I did that by keeping a pointer to the next item and a counter that we use to tell when we hit the end. You could also have kept track of the sate by keeping a pointer to theMyStructthe iterator is using and a counter for the position within that, something like:(In this instance we could actually have just used the container itself as the iterator as an iterator, by supplying an
__iter__()that returned a copy of the container and anext()similar to the first type. I didn’t do that in my original answer because I thought that would be less clear than have two distinct types – a container and an iterator for that container)