
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