Import C Code in Python (Part 2)

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.

Share

Leave a Reply

Your email address will not be published. Required fields are marked *