cython is a powerful tool to integrate c and python code; but if the only usage is to call c library code from python, there are other tools available; one such tool is ctypes; ctypes is a foreign function library that provides c compatible data types and allows calling functions in shared libraries; ctypes is distributed with python itself, so there is no need to install it before use;

example 1

the first example is about an adder function written in c file add.c:

int add(int a, int b)
{
    return a + b;
}

to call this function in python, we first compile it into a shared library:

gcc -shared -fPIC -o libadd.so add.c

the we call it in python like this:

from ctypes import cdll
libadd = cdll.LoadLibrary('./libadd.so')
print(libadd.add(3, 4))

output:

7

this is even simpler than using cython, right? we made no declarations; we did not have a setup.py; we just opened that dll and called the function inside, passing python objects as arguments;

as you can guess, there are auto type conversions behind the scene; ctypes can auto convert None, integers, bytes and strs to corresponding c data types; for a complete list of c data types defined by ctypes, follow this link;

example 2

the second example involves calling sin function from c library libm;

since this library is not made by ourselves, we do not know where it is; but we can find it using find_library:

from ctypes.util import find_library
print(find_library("m"))

output:

libm.so.6

now we can open this library and use its functions (for brevity, we omit print statements, same below):

libm = cdll.LoadLibrary('libm.so.6')
libm.sin(3.14/2)

but this gives us an error saying something like ctypes does not know how to convert the parameter;

here the c function sin has prototype double sin(double x); specifically, x has c double type, but we are passing an argument having python float type; this is not one of ( None, integers, bytes, strs ), so ctypes does not auto convert it; we have to wrap it in its corresponding c data type; here we convert python float type into c double type:

from ctypes import c_double
libm.sin(c_double(3.14/2))

this time it does not raise an error, but outputs 0;

alright, this is another problem: by default functions are assumed to return the c int type; we need to set the restype attribute of the function object to specify another return type; so we do that:

libm.sin.restype = c_double
libm.sin(c_double(3.14/2))

output:

0.9999996829318346

now it works;

in fact, we can set the argtypes attribute of the function object to specify its argument types, like this:

libm.sin.argtypes = [ c_double ]
libm.sin(3.14/2)

output:

0.9999996829318346

note that we omitted c_double in the argument list, but it still works because this time the function knows about argument types;

summary

these examples should have illustrated the basic usage of ctypes; of course it can do more; there are many topics we did not cover here: arrays, structs, unions, pointers, function pointers, etc.; ctypes supports all of them; more details can be found in its documentation;