Cython equivalent of c define #define myfunc(node x,...) SetNode(x.getattributeNode(),__VA_ARGS__)

1.6k views Asked by At

Cython equivalent of c define

#define myfunc(Node x,...) SetNode(x.getattributeNode(),__VA_ARGS__)

I have a c api SetNode which takes first argument a node of struct type node and N variables (N is variable number from 0-N)

here is a c example to solve such problum

exampleAPI.c

#include<stdarg.h>
float sumN(int len,...){
    va_list argp;
    int i;
    float s=0;
    va_start(argp,len);
    for(i=0;i<len;i++){
       s+=va_arg(argp,int);
    }
    va_end(argp);
}

exampleAPI.h

#include<stdarg.h>
float sumN(int len,...)

examplecode.c

#include<stdarg.h>
#include"exampleAPI.h"

int len(float first,...){
    va_list argp;
    int i=1;
    va_start(argp,first);
    while(1){
       if(va_arg(argp,float)==NULL){
            return i
       }
       else{
            i++;
       }
    }
    va_end(argp);
}

#define sum(...) sumN(len(__VA_ARGS__),__VA_ARGS__)

Now calling

sum(1,2,3,4);

will return 10.000000

sum(1.5,6.5);

will return 8.00000

I need a cython alternative for bellow c definition and not above example because I have a C-API which has SetNode function which takes variable number of arguments and I want to wrap it in cython and call from python

 #define myfunc(Node x,...) SetNode(x.getattributeNode(),__VA_ARGS__)

here Node is a class defined in cython which holds a c stuct as attribute and getattributeNode() is a function of Node class which returns c struct that needs to be passed into C-API.

cdef extern "Network.h":

    ctypedef struct node_bn:
         pass

    node_bn* SetNode(node_bn* node,...)


cdef class Node:

    cdef node_bn *node

    cdef getattributeNode(self):
        return self.node     

    def setNode(self,*arg):
        self.node=SetNode(self.node,*arg) # Error cannot convert python objects to c type

Alternative thing I tried

cdef extern from "stdarg.h":
   ctypedef struct va_list:
      pass
   ctypedef struct fake_type:
      pass
   void va_start(va_list, void* arg)
   void* va_arg(va_list, fake_type)
   void va_end(va_list)
   fake_type int_type "int" 

cdef extern "Network.h":

    ctypedef struct node_bn:
         pass

    node_bn* VSetNode(node_bn* node,va_list argp)


cdef class Node:

    cdef node_bn *node

    cdef getattributeNode(self):
        return self.node     

    cpdef _setNode(self,node_bn *node,...):
        cdef va_list agrp
        va_start(va_list, node)       
        self.node=VSetNode(node,argp) 
        va_end(va_list)

    def setNode(self,*arg):
        self._setNode(self.node,*arg) 

works fine when argument list is empty

n = Node()
n.setNode() #This works
n.SetNode("top",1) # error takes exactly one argument given 3 in  self._setNode(self.node,*arg)

If anyone could suggest cython equivalent to it, it would be great.

2

There are 2 answers

0
DavidW On BEST ANSWER

I don't think it's easily done though Cython (the problem is telling Cython what type conversions to do for an arbitrary number of arguments). The best I can suggest is to use the standard library ctypes library for this specific case and wrap the rest in Cython.

For the sake of an example, I've used a very simple sum function. va_sum.h contains:

typedef struct { double val; } node_bn;

node_bn* sum_va(node_bn* node,int len, ...);
/* on windows this must be:
__declspec(dllexport) node_bn* sum_va(node_bn* node,int len, ...);
*/

and va_sum.c contains:

#include <stdarg.h>

#include "va_sum.h"

node_bn* sum_va(node_bn* node,int len, ...) {
    int i;
    va_list vl;
    va_start(vl,len);
    for (i=0; i<len; ++i) {
        node->val += va_arg(vl,double);
    }
    va_end(vl);
    return node;
}

I've written it so it adds everything to a field in a structure just to demonstrate that you can pass pointers to structures without too much trouble.

The Cython file is:

# definition of a structure
cdef extern from "va_sum.h":   

    ctypedef struct node_bn:
        double val;

# obviously you'll want to wrap things in terms of Python accessible classes, but this atleast demonstrates how it works
def test_sum(*args):
    cdef node_bn input_node;
    cdef node_bn* output_node_p;
    input_node.val = 5.0 # create a node, and set an initial value

    from ctypes import CDLL, c_double,c_void_p
    import os.path
    # load the Cython library with ctypes to gain access to the "sum_va" function
    # Assuming you've linked it in when you build the Cython module
    full_path = os.path.realpath(__file__)
    this_file_library = CDLL(full_path)

    # I treat all my arguments as doubles - you may need to do
    # something more sophisticated, but the idea is the same:
    # convert them to the c-type the function is expecting
    args = [ c_double(arg) for arg in args ]
    sum_va = this_file_library.sum_va
    sum_va.restype = c_void_p # it returns a pointer

    # pass the pointers as a void pointer
    # my c compiler warns me if I used int instead of long
    # but which integer type you have to use is likely system dependent
    # and somewhere you have to be careful
    output_node_p_as_integer = sum_va(c_void_p(<long>&input_node),len(args),
                           *args)

    # unfortunately getting the output needs a two stage typecast - first to a long, then to a pointer
    output_node_p = <node_bn*>(<long>(output_node_p_as_integer))
    return output_node_p.val

You need to compile your va_sum.c together with your Cython file (e.g. by adding sources = ['cython_file.pyx','va_sum.c'] in setup.py)

Ctypes is probably a bit slower than Cython (I think there's a reasonable overhead on each call), and it's odd to mix them, but this should at least let you write the main wrapper in Cython, and use ctypes to get round the specific limitation.

2
Pynchia On

This is probably not the proper answer, since I am not sure I understand the question fully. I would have replied in a comment, but the code formatting is too poor. In Python the functions sum and len are available:

def my_len(*args):
    return len(args)

def my_sum(*args):
    return sum(args)

print "len =", my_len("hello", 123, "there")
print "sum =", my_sum(6.5, 1.5, 2.0)

outputs:

len = 3
sum = 10.0