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 :
# 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 * ) |
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 :
# 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 |
Code #3 :
# 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) |
Finally, to build the extension module, create a work.py
file.
Code #4:
# 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) |
Code #5 : Building resulting module for experimentation.
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 % |
Now, we have an extension module work.so
. Let’s see how it works.
Code #6 :
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)) |
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.