Importing C libraries and functions in Python using ctypes library and Cython
C is known by its speed, so sometimes when the performance and the speed are an issue, developers go to write complex functions or loops in C to get the benefits of a low-level language.
We will explore two ways to run C code from within Python. We will achieve that using two methods: ctypes library and Cython language which is a superset of python that compiles into C.
In part 1 of the post we explored ctypes. While here in part 2, we will do a more complex task with Cython.
Read C Scripts into Python through Cython
Cython is a superset of python that compiles to C code to boost speed. Designed to give C-like performance with code that is written mostly in Python.
Cython can be considered a language of its own that has its own syntax, even though it is so similar to python syntax, that’s why it is somewhat more difficult than simple ways to integrate C with python such as ctypes for example. But it gives so much flexibility and sometimes using Cython code and importing it into python gives same results as pure C code.
Let’s get into business now!
Let’s say we have written Stack data structure in C and would like to use it inside our python script.
See Struct Pie for more data structures written in C to be used in Python.
C Stack Scripts
Our header file, “stack.h” looks like:
/* A Stack that accepts int, float and str data types */ #define INIT_SIZE 1000 // enum to switch between types typedef enum { INT, FLOAT, STR } Type; typedef union { int * _int; float * _float; char _str[INIT_SIZE][100]; } arrays; // initialize a stack struct stck { Type type; // union to have only one memory allocated for all arrays val; int indx; int lindx; int size; }; typedef struct stck stack; void create_stack(stack * st, int input_type); int is_empty(stack * st); // pushing functions for each type argument void push_int(stack * st, int new_val); void push_float(stack * st, float new_val); void push_str(stack * st, char * new_val); // poping functions for each type argument int pop_int(stack * st, int input); float pop_float(stack * st, float input); char * pop_str(stack * st, char * input); // some helper functions void print_stack(stack * st); int len(stack * st); void print_if_empty(stack * st);
While the “stack.c” script is:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include "stack.h" void create_stack(stack * st, int input_type) { // switch based on input type switch (input_type) { case 2: // populate enum // str is a an array of strings without malloc st -> type = STR; break; case 1: st -> type = FLOAT; // memory allocation st -> val._float = malloc(sizeof(float) * INIT_SIZE); break; case 0: st -> type = INT; st -> val._int = (int * ) malloc(sizeof(int) * INIT_SIZE); break; default: puts("\n Invalid Input"); } // generic actions st -> indx = -1; st -> size = 0; st -> lindx = 0; } // check empty int is_empty(stack * st) { if (st -> size == 0) return 1; else return 0; } // push/append int void push_int(stack * st, int new_val) { if (st -> size >= INIT_SIZE) st -> val._int = (int * ) realloc(st -> val._int, sizeof(int) * (st -> size + INIT_SIZE)); st -> indx++; st -> val._int[st -> indx] = new_val; st -> size++; } // push/append float void push_float(stack * st, float new_val) { if (st -> size >= INIT_SIZE) st -> val._float = realloc(st -> val._float, sizeof(float) * (st -> size + INIT_SIZE)); st -> indx++; st -> val._float[st -> indx] = new_val; st -> size++; } // push/append char* void push_str(stack * st, char * new_val) { st -> indx++; strcpy(st -> val._str[st -> indx], new_val); st -> size++; } // pop int int pop_int(stack * st, int input) { if (is_empty(st)) { puts("STACK IS EMPTY"); return -1; } else { int popped = st -> val._int[st -> indx]; st -> indx--; st -> size--; return popped; } } // pop float float pop_float(stack * st, float input) { if (is_empty(st)) { puts("STACK IS EMPTY"); return -1.; } else { float popped = st -> val._float[st -> indx]; st -> indx--; st -> size--; return popped; } } // pop char* char * pop_str(stack * st, char * input) { if (is_empty(st)) { puts("STACK IS EMPTY"); return "-1"; } else { char * popped = st -> val._str[st -> indx]; st -> indx--; st -> size--; return popped; } } // print stack void print_stack(stack * st) { printf("["); int i; // switch to print switch (st -> type) { case INT: for (i = st -> lindx; i <= st -> indx; i++) { printf("%i ", st -> val._int[i]); } break; case FLOAT: for (i = st -> lindx; i <= st -> indx; i++) { printf("%.2f ", st -> val._float[i]); } break; case STR: for (i = st -> lindx; i <= st -> indx; i++) { printf("%s, ", st -> val._str[i]); } break; } printf("]\n"); } // length int len(stack * st) { return st -> size; } void print_if_empty(stack * st) { int empty = is_empty(st); if (empty) puts("\n\tStack is empty\n"); else puts("\n\tStack is not empty now\n"); }
Our Stack accepts all data types; str, int and float.
Cython Wrapper
Now we will write a Cython wrapper to wrap the C Structs and functions. In a Cython script i.e. a “.pyx” file, we should import the structs and the functions from the header file and wrap the C struct into a python Class and use the C functions as Class methods.
Here is how our “stack.pyx” file looks like:
from libc.stdlib cimport malloc DEF INIT_SIZE = 1000 ## defining the c structs, enums, unions ## and functions from the header cdef extern from "stack.h": ctypedef enum Type: INT, FLOAT, STR cdef union arrays: int * _int float * _float char _str[INIT_SIZE][100] cdef struct st: Type type arrays val int indx int lindx int size ctypedef st stack void create_stack(stack * st, int input_type) int is_empty(stack * st) # pushing functions for each type argument void push_int(stack * st, int new_val) void push_float(stack * st, float new_val) void push_str(stack * st, char * new_val) # poping functions for each type argument int pop_int(stack * st, int input) float pop_float(stack * st, float input) char * pop_str(stack * st, char * input) void print_stack(stack * st) int len(stack * st) ## wrapping everything inside a python class cdef class Stack: ## declaring the stack object cdef stack *st cdef int input ## initiating it allocating memory with the create_stack function def __cinit__(self, str inp): enums = {"int": 0, "float": 1, "str": 2} self.input = enums.get(inp) if not input: print("Invalid Input\nStack will be of INT type by default!") self.input = 0 self.st = <stack*>malloc(sizeof(stack)) create_stack(self.st, self.input) def empty(self): if is_empty(self.st) == 1: return True else: return False ## push new data based on type it executes relevant function def push(self, new_val): if isinstance(new_val, int): push_int(self.st, new_val) elif isinstance(new_val, float): push_float(self.st, new_val) else: push_str(self.st, new_val.encode()) ## same with popping def pop(self): if self.input == 0: return pop_int(self.st, 0) elif self.input == 1: return round(pop_float(self.st, 1.0), 2) else: return pop_str(self.st, "2") ## print the stack and return length cpdef void display(self): print_stack(self.st) cpdef int length(self): return len(self.st)
Great!
Now we have everything ready to start using our C Stack in Python. Only one step is left which is compiling the Cython code and create the python module to be used/imported inside a python script. This is done with a setup.py file.
Creating the Python Module
The setup.py file is so simple:
from distutils.core import setup from distutils.extension import Extension from Cython.Distutils import build_ext ext_modules = [Extension("pystack", ["pystack.pyx", "stack.c"])] setup( cmdclass = {"build_ext": build_ext}, ext_modules = ext_modules )
Then, from the command line we run:
python3 setup.py build_ext --inplace
The “pystack” should be created now.
Final Step: Reading the Module in Python
Now we can use the “pystack” module in python just like any other python library/module.
from pystack import * # initiate a stack of type "int" st = Stack("int") # push values into the stack st.push(13) st.push(11) st.push(19) st.push(91) # print the stack st.display() # [13 11 19 91 ] # pop from right (LIFO) st.pop() # 91 # check length st.length() # 3 # print again st.display() # [13 11 19 ]
Wohoooo! Great work!
We now have a Stack data structure written in C and working as purely Python code.
Leave a Reply