Cython to Wrap Existing C Code

What is Cython ?
It is an optimizing static compiler for both the Python programming language and the extended Cython programming language. It is used to make it easy to write C extensions for Python as easy as Python itself.

It comes up with many helpful features :

  • Writing a Python code that calls back and forth from and to C/C++ code.
  • Easily tuning of readable Python code into plain C performance by adding static type declarations.
  • Use of combined source code level debugging to find bugs in given Python, Cython and C code.
  • Efficient interaction with large data sets, e.g. using multi-dimensional NumPy arrays.
  • Integration with existing code and data from low-level or high-performance libraries and applications.

To make an extension with Cython is a tricky task to perform. Doing so, one needs to create a collection of wrapper functions. Assuming that the work code shown has been compiled into a C library called libwork. The code below will create a file named csample.pxd.



Code #1 :

filter_none

edit
close

play_arrow

link
brightness_4
code

# cwork.pxd
#
# Declarations of "external" C 
# functions and structures
  
cdef extern from "work.h":
   
    int gcd(int, int)
    int divide(int, int, int *)
    double avg(double *, int) nogil
      
    ctypedef struct Point:
        double x
        double y
          
    double distance(Point *, Point *)

chevron_right


In Cython, the code above will work as a C header file. The initial declaration cdef extern from "work.h" declares the required C header file. Declarations that follow are taken from the header. The name of this file is cwork.pxd. Next target is to create a work.pyx file which will define wrappers that bridge the Python interpreter to the underlying C code declared in the cwork.pxd file.

Code #2 :

filter_none

edit
close

play_arrow

link
brightness_4
code

# work.pyx
# Import the low-level C declarations
        
cimport cwork
# Importing functionalities from Python
# and the C stdlib
from cpython.pycapsule cimport * 
from libc.stdlib cimport malloc, free
  
# Wrappers
def gcd(unsigned int x, unsigned int y):
    return cwork.gcd(x, y)
  
def divide(x, y):
    cdef int rem
    quot = cwork.divide(x, y, &rem)
    return quot, rem
  
def avg(double[:] a):
    cdef:
        int sz
        double result
  
    sz = a.size
  
    with nogil:
        result = cwork.avg(<double *> &a[0], sz)
  
    return result

chevron_right


 
Code #3 :

filter_none

edit
close

play_arrow

link
brightness_4
code

# Destructor for cleaning up Point objects
cdef del_Point(object obj):
    pt = <csample.Point *> PyCapsule_GetPointer(obj, "Point")
    free(<void *> pt)
      
# Create a Point object and return as a capsule
def Point(double x, double y):
    cdef csample.Point * p
    p = <csample.Point *> malloc(sizeof(csample.Point))
      
    if p == NULL:
        raise MemoryError("No memory to make a Point")
          
    p.x = x
    p.y = y
      
    return PyCapsule_New(<void *>p, "Point",
                         <PyCapsule_Destructor>del_Point)
  
def distance(p1, p2):
    pt1 = <csample.Point *> PyCapsule_GetPointer(p1, "Point")
    pt2 = <csample.Point *> PyCapsule_GetPointer(p2, "Point")
      
    return csample.distance(pt1, pt2)

chevron_right


 
Finally, to build the extension module, create a work.py file.

Code #4:

filter_none

edit
close

play_arrow

link
brightness_4
code

# importing libraries
from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext
  
ext_modules = [Extension('work'
                         ['work.pyx'], 
                         libraries=['work'], 
                         library_dirs=['.'])]
  
setup(name = 'work extension module',
      cmdclass = {'build_ext': build_ext},
      ext_modules = ext_modules)

chevron_right


 
Code #5 : Building resulting module for experimentation.

filter_none

edit
close

play_arrow

link
brightness_4
code

bash % python3 setup.py build_ext --inplace
running build_ext
  
cythoning work.pyx to work.c
building 'work' extension
  
gcc -fno-strict-aliasing -DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes
-I/usr/local/include/python3.3m -c work.c
-o build/temp.macosx-10.6-x86_64-3.3/work.o
  
gcc -bundle -undefined dynamic_lookup build/temp.macosx-10.6-x86_64-3.3/work.o
-L. -lwork -o work.so
bash %

chevron_right


Now, we have an extension module work.so. Let’s see how it works.

Code #6 :

filter_none

edit
close

play_arrow

link
brightness_4
code

import sample
print ("GCD : ", sample.gcd(12, 8))
  
print ("\nDivision : ", sample.divide(42,10))
  
import array
arr = array.array('d',[1,2,3])
print ("\nAverage  : ", sample.avg(a)
  
pt1 = sample.Point(2,3)
pt2 = sample.Point(4,5)
  
print ("\npt1 : ", pt1)
print ("\npt2 : ", pt2)
  
print ("\nDistance between the two points : "
       sample.distance(pt1, pt2))

chevron_right


Output :

GCD : 4

Division : (4, 2)

Average : 2.0

pt1 : <capsule object "Point" at 0x1005d1e70>

pt2 : <capsule object "Point" at 0x1005d1ea0>

Distance between the two points : 2.8284271247461903

At a high level, using Cython is modeled after C. The .pxd files merely contain C definitions (similar to .h files) and the .pyx files contain implementation (similar to a .c file). The cimport statement is used by Cython to import definitions from a .pxd file. This is different than using a normal Python import statement, which would load a regular Python module.



My Personal Notes arrow_drop_up

Check out this Author's contributed articles.

If you like GeeksforGeeks and would like to contribute, you can also write an article using contribute.geeksforgeeks.org or mail your article to contribute@geeksforgeeks.org. See your article appearing on the GeeksforGeeks main page and help other Geeks.

Please Improve this article if you find anything incorrect by clicking on the "Improve Article" button below.


Article Tags :

Be the First to upvote.


Please write to us at contribute@geeksforgeeks.org to report any issue with the above content.