In this section, we will create a function to square all the values of the NumPy Array. The aim here is to demonstrate how to get a NumPy Array in C and then iterate over it. In a real-world scenario, this can be done in an easier way using a map or by vectorizing a square function. We are using the same PyArg_ParseTuple
function with the O!
format string. This format string has a (object) [typeobject, PyObject *]
signature and takes the Python type object as the first argument. Users should go through the official API doc to take a look at what other format strings are permissible and which one suits their needs:
The following code snippet explain how to parse the argument using PyArg_ParseTuple
.
// Implementation of square of numpy array static PyObject* square_nparray_func(PyObject* self, PyObject* args) { // variable declarations PyArrayObject *in_array; PyObject *out_array; NpyIter *in_iter; NpyIter *out_iter; NpyIter_IterNextFunc *in_iternext; NpyIter_IterNextFunc *out_iternext; // Parse the argument tuple by specifying type "object" and putting the reference in in_array if (!PyArg_ParseTuple(args, "O!", &PyArray_Type, &in_array)) return NULL; ...... ......
The next step is to create an array to store its output value and iterators in order to iterate on Numpy Arrays. Note that there is a {handle failure}
code at each step when we create an object. This is to ensure that, if anything goes wrong, we can pinpoint the location of the faulty code via debugging:
//Construct the output from the new constructed input array out_array = PyArray_NewLikeArray(in_array, NPY_ANYORDER, NULL, 0); // Test it and if the input is nothing then just return nothing. {handle failure} // Create the iterators in_iter = NpyIter_New(in_array, NPY_ITER_READONLY, NPY_KEEPORDER, NPY_NO_CASTING, NULL); // {handle failure} out_iter = NpyIter_New((PyArrayObject *)out_array, NPY_ITER_READWRITE, NPY_KEEPORDER, NPY_NO_CASTING, NULL); {handle failure} in_iternext = NpyIter_GetIterNext(in_iter, NULL); out_iternext = NpyIter_GetIterNext(out_iter, NULL); {handle failure} double ** in_dataptr = (double **) NpyIter_GetDataPtrArray(in_iter); double ** out_dataptr = (double **) NpyIter_GetDataPtrArray(out_iter); A simple handle failure module is like // {Start handling failure} if (in_iter == NULL) // remove the ref and return null Py_XDECREF(out_array); return NULL; // {End handling failure}
After taking a look at the preceding boilerplate code, we finally come to the part where all the real action takes place. Those of you who are familiar with C++ will find the method of iteration to be similar to the iteration done over vectors. The in_iternext
function that we have defined previously comes in handy here and is used to iterate over the Numpy Array. After our while loop, we make sure that we call NpyIter_Deallocate
on both iterators and Py_INCREF
on the output array; failing to call these functions is the most common type of mistake that causes memory leaks. Memory leak problems are mostly quite subtle and normally make an appearance when you have a long-running code (such as services or a daemon). To catch these, there is unfortunately no easier way than using a debugger and looking deeper. Sometimes, it helps to just write a couple of printf
statements, which output the total memory usage:
/* iterate over the arrays */ do { **out_dataptr =pow(**in_dataptr,2); } while(in_iternext(in_iter) && out_iternext(out_iter)); /* clean up and return the result */ NpyIter_Deallocate(in_iter); NpyIter_Deallocate(out_iter); Py_INCREF(out_array); return out_array;
3.141.19.185