Basic Tutorial

Note

This page uses two different syntax variants:

  • Cython specificcdef syntax, which was designed to make type declarationsconcise and easily readable from a C/C++ perspective.

  • Pure Python syntax which allows static Cython type declarations inpure Python code,followingPEP-484 type hintsandPEP 526 variable annotations.

    To make use of C data types in Python syntax, you need to import the specialcython module in the Python module that you want to compile, e.g.

    importcython

    If you use the pure Python syntax we strongly recommend you use a recentCython 3 release, since significant improvements have been made herecompared to the 0.29.x releases.

The Basics of Cython

The fundamental nature of Cython can be summed up as follows: Cython is Pythonwith C data types.

Cython is Python: Almost any piece of Python code is also valid Cython code.(There are a fewLimitations, but this approximation willserve for now.) The Cython compiler will convert it into C code which makesequivalent calls to the Python/C API.

But Cython is much more than that, because parameters and variables can bedeclared to have C data types. Code which manipulatesPython values and Cvalues can be freely intermixed, with conversions occurring automaticallywherever possible. Reference count maintenance and error checking of Pythonoperations is also automatic, and the full power of Python’s exceptionhandling facilities, including the try-except and try-finally statements, isavailable to you – even in the midst of manipulating C data.

Cython Hello World

As Cython can accept almost any valid python source file, one of the hardestthings in getting started is just figuring out how to compile your extension.

So lets start with the canonical python hello world:

print("Hello World")

Save this code in a file namedhelloworld.py. Now we need to createthesetup.py, which is like a python Makefile (for more informationseeSource Files and Compilation). Yoursetup.py should look like:

fromsetuptoolsimportsetupfromCython.Buildimportcythonizesetup(ext_modules=cythonize("helloworld.py"))

To use this to build your Cython file use the commandline options:

$ python setup.py build_ext --inplace

Which will leave a file in your local directory calledhelloworld.so in unixorhelloworld.pyd in Windows. Now to use this file: start the pythoninterpreter and simply import it as if it was a regular python module:

>>>importhelloworldHelloWorld

Congratulations! You now know how to build a Cython extension. But so farthis example doesn’t really give a feeling why one would ever want to use Cython, solets create a more realistic example.

Fibonacci Fun

From the official Python tutorial a simple fibonacci function is defined as:

deffib(n):"""Print the Fibonacci series up to n."""a,b=0,1whileb<n:print(b,end=' ')a,b=b,a+bprint()

Now following the steps for the Hello World example we first save this codeto a Python file, let’s sayfibonacci.py. Next, we create thesetup.py file. Using the file created for the Hello World example, allthat you need to change is the name of the Cython filename, and the resultingmodule name, doing this we have:

fromsetuptoolsimportsetupfromCython.Buildimportcythonizesetup(ext_modules=cythonize("fibonacci.py"),)

Build the extension with the same command used for the “helloworld.py”:

$ python setup.py build_ext --inplace

And use the new extension with:

>>>importfibonacci>>>fibonacci.fib(2000)11235813213455891442333776109871597

Primes

Here’s a small example showing some of what can be done. It’s a routine forfinding prime numbers. You tell it how many primes you want, and it returnsthem as a Python list.

primes.py
 1defprimes(nb_primes:cython.int): 2i:cython.int 3p:cython.int[1000] 4 5ifnb_primes>1000: 6nb_primes=1000 7 8ifnotcython.compiled:# Only if regular Python is running 9p=[0]*1000# Make p work almost like a C array1011len_p:cython.int=0# The current number of elements in p.12n:cython.int=213whilelen_p<nb_primes:14# Is n prime?15foriinp[:len_p]:16ifn%i==0:17break1819# If no break occurred in the loop, we have a prime.20else:21p[len_p]=n22len_p+=123n+=12425# Let's copy the result into a Python list:26result_as_list=[primeforprimeinp[:len_p]]27returnresult_as_list

The two syntax variants (“Pure Python” and “Cython”) represent different waysof annotating the code with C data types. The first uses regular Python syntaxwith Cython specific type hints, thus allowing the code to run as a normalPython module. Python type checkers will ignore most of the type details,but on compilation, Cython interprets them as C data types and uses themto generate tightly adapted C code.

The second variant uses a Cython specific syntax. This syntax is mostly usedin older code bases and in Cython modules that need to make use of advancedC or C++ features when interacting with C/C++ libraries. The additions tothe syntax require a different file format, thus the.pyx extension:Python code ‘extended’.

For this tutorial, assuming you have a Python programming background,it’s probably best to stick to the Python syntax examplesand glimpse at the Cython specific syntax for comparison.

You can see that the example above starts out just like a normal Python functiondefinition, except that the parameternb_primes is declared to be of Cythontypeint (not the Python type of the same name). This means that the Pythonobject passed into the function will be converted to a C integer on entry,or aTypeError will be raised if it cannot be converted.

Now, let’s dig into the core of the function:

2i:cython.int3p:cython.int[1000]
11len_p:cython.int=0# The current number of elements in p.12n:cython.int=2

Lines 2, 3, 11 and 12 use the variable annotationsto define some local C variables.The result is stored in the C arrayp during processing,and will be copied into a Python list at the end (line 26).

Note

You cannot create very large arrays in this manner, becausethey are allocated on the C function callstack,which is a rather precious and scarce resource.To request larger arrays,or even arrays with a length only known at runtime,you can learn how to make efficient use ofC memory allocation,Python arraysorNumPy arrays with Cython.

5ifnb_primes>1000:6nb_primes=1000

As in C, declaring a static array requires knowing the size at compile time.We make sure the user doesn’t set a value above 1000 (or we would have asegmentation fault, just like in C)

8ifnotcython.compiled:# Only if regular Python is running9p=[0]*1000# Make p work almost like a C array

When we run this code from Python, we have to initialize the items in the array.This is most easily done by filling it with zeros (as seen on line 8-9).When we compile this with Cython, on the other hand, the array willbehave as in C. It is allocated on the function call stack with a fixedlength of 1000 items that contain arbitrary data from the last time thatmemory was used. We will then overwrite those items in our calculation.

10len_p:cython.int=0# The current number of elements in p.11n:cython.int=212whilelen_p<nb_primes:

Lines 11-13 set up a while loop which will test numbers-candidates to primesuntil the required number of primes has been found.

14# Is n prime?15foriinp[:len_p]:16ifn%i==0:17break

Lines 15-16, which try to divide a candidate by all the primes found so far,are of particular interest. Because no Python objects are referred to,the loop is translated entirely into C code, and thus runs very fast.You will notice the way we iterate over thep C array.

15foriinp[:len_p]:

The loop gets translated into a fast C loop and works just like iteratingover a Python list or NumPy array. If you don’t slice the C array with[:len_p], then Cython will loop over the 1000 elements of the array.

19# If no break occurred in the loop, we have a prime.20else:21p[len_p]=n22len_p+=123n+=1

If no breaks occurred, it means that we found a prime, and the block of codeafter theelse line 20 will be executed. We add the prime found top.If you find having anelse after a for-loop strange, just know that it’s alesser known features of the Python language, and that Cython executes it atC speed for you.If the for-else syntax confuses you, see this excellentblog post.

25# Let's copy the result into a Python list:26result_as_list=[primeforprimeinp[:len_p]]27returnresult_as_list

In line 26, before returning the result, we need to copy our C array into aPython list, because Python can’t read C arrays. Cython can automaticallyconvert many C types from and to Python types, as described in thedocumentation ontype conversion, so we can usea simple list comprehension here to copy the Cint values into a Pythonlist of Pythonint objects, which Cython creates automatically along the way.You could also have iterated manually over the C array and usedresult_as_list.append(prime), the result would have been the same.

You’ll notice we declare a Python list exactly the same way it would be in Python.Because the variableresult_as_list hasn’t been explicitly declared with a type,it is assumed to hold a Python object, and from the assignment, Cython also knowsthat the exact type is a Python list.

Finally, at line 27, a normal Python return statement returns the result list.

Compiling primes.py with the Cython compiler produces an extension modulewhich we can try out in the interactive interpreter as follows:

>>>importprimes>>>primes.primes(10)[2, 3, 5, 7, 11, 13, 17, 19, 23, 29]

See, it works! And if you’re curious about how much work Cython has saved you,take a look at the C code generated for this module.

Cython has a way to visualise where interaction with Python objects andPython’s C-API is taking place. For this, pass theannotate=True parameter tocythonize(). It produces a HTML file. Let’s see:

../../_images/htmlreport_py1.png

If a line is white, it means that the code generated doesn’t interactwith Python, so will run as fast as normal C code. The darker the yellow, the morePython interaction there is in that line. Those yellow lines will usually operateon Python objects, raise exceptions, or do other kinds of higher-level operationsthan what can easily be translated into simple and fast C code.The function declaration and return use the Python interpreter so it makessense for those lines to be yellow. Same for the list comprehension becauseit involves the creation of a Python object. But the lineifn%i==0:, why?We can examine the generated C code to understand:

../../_images/python_division.png

We can see that some checks happen. Because Cython defaults to thePython behavior, the language will perform division checks at runtime,just like Python does. You can deactivate those checks by using thecompiler directives.

Now let’s see if we get a speed increase even if there is a division check.Let’s write the same program, but in Python:

primes_python.py / primes_python_compiled.py
defprimes(nb_primes):p=[]n=2whilelen(p)<nb_primes:# Is n prime?foriinp:ifn%i==0:break# If no break occurred in the loopelse:p.append(n)n+=1returnp

It is possible to take a plain (unannotated).py file and to compile it with Cython.Let’s create a copy ofprimes_python and name itprimes_python_compiledto be able to compare it to the (non-compiled) Python module.Then we compile that file with Cython, without changing the code.Now thesetup.py looks like this:

fromsetuptoolsimportsetupfromCython.Buildimportcythonizesetup(ext_modules=cythonize(['primes.py',# Cython code file with primes() function'primes_python_compiled.py'],# Python code file with primes() functionannotate=True),# enables generation of the html annotation file)

Now we can ensure that those two programs output the same values:

>>>importprimes,primes_python,primes_python_compiled>>>primes_python.primes(1000)==primes.primes(1000)True>>>primes_python_compiled.primes(1000)==primes.primes(1000)True

It’s possible to compare the speed now:

python-mtimeit-s"from primes_python import primes""primes(1000)"10loops,bestof3:23msecperlooppython-mtimeit-s"from primes_python_compiled import primes""primes(1000)"100loops,bestof3:11.9msecperlooppython-mtimeit-s"from primes import primes""primes(1000)"1000loops,bestof3:1.65msecperloop

The cythonize version ofprimes_python is 2 times faster than the Python one,without changing a single line of code.The Cython version is 13 times faster than the Python version! What could explain this?

Multiple things:
  • In this program, very little computation happen at each line.So the overhead of the python interpreter is very important. It would bevery different if you were to do a lot computation at each line. Using NumPy forexample.

  • Data locality. It’s likely that a lot more can fit in CPU cache when using C thanwhen using Python. Because everything in python is an object, and every object isimplemented as a dictionary, this is not very cache friendly.

Usually the speedups are between 2x to 1000x. It depends on how much you callthe Python interpreter. As always, remember to profile before adding typeseverywhere. Adding types makes your code less readable, so use them withmoderation.

Primes with C++

With Cython, it is also possible to take advantage of the C++ language, notably,part of the C++ standard library is directly importable from Cython code.

Let’s see what our code becomes when usingvectorfrom the C++ standard library.

Note

Vector in C++ is a data structure which implements a list or stack basedon a resizeable C array. It is similar to the Pythonarraytype in thearray standard library module.There is a methodreserve available which will avoid copies if you know in advancehow many elements you are going to put in the vector. For more detailsseethis page from cppreference.

 1# distutils: language=c++ 2 3importcython 4fromcython.cimports.libcpp.vectorimportvector 5 6defprimes(nb_primes:cython.uint): 7i:cython.int 8p:vector[cython.int] 9p.reserve(nb_primes)# allocate memory for 'nb_primes' elements.1011n:cython.int=212whilep.size()<nb_primes:# size() for vectors is similar to len()13foriinp:14ifn%i==0:15break16else:17p.push_back(n)# push_back is similar to append()18n+=11920# If possible, C values and C++ objects are automatically21# converted to Python objects at need.22returnp# so here, the vector will be copied into a Python list.

Warning

The code provided above / on this page uses an externalnative (non-Python) library through acimport (cython.cimports).Cython compilation enables this, but there is no support for this fromplain Python. Trying to run this code from Python (without compilation)will fail when accessing the external library.This is described in more detail inCalling C functions.

The first line is a compiler directive. It tells Cython to compile your code to C++.This will enable the use of C++ language features and the C++ standard library.Note that it isn’t possible to compile Cython code to C++ withpyximport. Youshould use asetup.py or a notebook to run this example.

You can see that the API of a vector is similar to the API of a Python list,and can sometimes be used as a drop-in replacement in Cython.

For more details about using C++ with Cython, seeUsing C++ in Cython.

Language Details

For more about the Cython language, seeLanguage Basics.To dive right in to using Cython in a numerical computation context,seeTyped Memoryviews.