EuroSciPy 2015, Stefan Behnel

In [ ]:
%load_ext cython
In [ ]:
import sys
import Cython
print("Python %d.%d.%d %s %s" % sys.version_info)
print("Cython %s" % Cython.__version__)

Building Cython modules

In [ ]:
# simple setup without elaborate dependencies
from distutils.core import setup
from Cython.Build import cythonize

setup(
    ext_modules = cythonize("*.pyx"),  # <- glob pattern
)
In [ ]:
# more complex case with per-module configuration
from distutils.core import setup
from Cython.Build import cythonize
from distutils.extension import Extension

ext_modules = [
    Extension(
        ["mypackage/mymodule.pyx"],
        ...  # configure C build + libraries here
    )]

setup(ext_modules = cythonize(ext_modules))

Functions and Coercion

In [ ]:
%%cython

def pyfunc(x):
    return x + 1

def cyfunc(int x):
    return x + 1

cdef int cfunc(int x):
    return x + 1

cpdef cypyfunc(int x):
    y = cfunc(x + 1)
    return y * 2
In [ ]:
pyfunc(2)
In [ ]:
cyfunc(2)
In [ ]:
cfunc(2)
In [ ]:
cypyfunc(2)

Static typing and type inference

In [ ]:
import math

def sin(x):
    return math.sin(x)
In [ ]:
%%cython -a
cimport libc.math

def sin(double x):
    return libc.math.sin(x)
In [ ]:
%%cython
def local_variables(x):
    cdef int i = 5, ix = x
    print(i * ix)
    return (i + ix) // 2
In [ ]:
local_variables(2)

Exercise: optimise Python code into C code

Optimise this code so that it compiles to plain C code but uses as few type declarations as possible.

In [ ]:
import math

def py_circular_distance(radius, lon1, lat1, lon2, lat2):
    x = math.pi/180.0
    a = (90.0-lat1) * x
    b = (90.0-lat2) * x
    theta = (lon2-lon1) * x
    c = math.acos((math.cos(a)*math.cos(b)) + (math.sin(a)*math.sin(b)*math.cos(theta)))
    return radius*c
In [ ]:
%%cython -a
import math

def cy_circular_distance(radius, lon1, lat1, lon2, lat2):
    x = math.pi/180.0
    a = (90.0-lat1) * x
    b = (90.0-lat2) * x
    theta = (lon2-lon1) * x
    c = math.acos((math.cos(a)*math.cos(b)) + (math.sin(a)*math.sin(b)*math.cos(theta)))
    return radius*c
In [ ]:
print(py_circular_distance(10, 1.2, 2, 2, 4.3))
print(cy_circular_distance(10, 1.2, 2, 2, 4.3))
In [ ]:
%timeit py_circular_distance(10, 1.2, 2, 2, 4.3)
In [ ]:
%timeit cy_circular_distance(10, 1.2, 2, 2, 4.3)

Calling C functions

In [ ]:
%%cython -a
# libc math functions
from libc cimport math

print( math.sin(math.M_PI / 2) )
In [ ]:
%%cython
# dynamic C memory allocation
from libc.stdlib cimport malloc, free

cdef int* cmem = <int*>malloc(22 * sizeof(int))
if not cmem:
    raise MemoryError()
free(cmem)
In [ ]:
%%cython
# dynamic CPython heap memory allocation
from cpython.mem cimport PyMem_Malloc, PyMem_Free

cdef int* pymem = <int*>PyMem_Malloc(22 * sizeof(int))
if not pymem:
    raise MemoryError()
    
try:
    pymem[:] = [1,2,3]
    print( pymem[0] + pymem[2] )
finally:
    PyMem_Free(pymem)

Auto-wrapping C functions to Python

In [ ]:
%%cython -a
from libc cimport math, stdlib
py_sin = math.sin
py_atoi = stdlib.atoi
In [ ]:
py_sin(1/2)
In [ ]:
py_atoi(b'123')

External libraries

In [ ]:
%%cython
# distutils: include_dirs=/usr/include/luajit-2.0
# distutils: libraries=luajit-5.1

## distutils: include_dirs=/usr/include/lua5.1
## distutils: libraries=lua5.1

cdef extern from "lua.h":
    ctypedef struct lua_State
    lua_State *luaL_newstate ()
    void lua_close (lua_State *L)
    int luaL_loadbuffer (lua_State *L, char *buff, size_t sz, char *name)
    void lua_settop (lua_State *L, int idx)
    int lua_gettop (lua_State *L)
    int lua_pcall (lua_State *L, int nargs, int nresults, int errfunc)
    int lua_type (lua_State *L, int idx)
    float lua_tonumber (lua_State *L, int idx)
    enum:
        LUA_TNUMBER
        LUA_MULTRET


def run_lua(code):
    cdef int result_status
    cdef float result

    if isinstance(code, unicode):
        code = code.encode('utf8')
    elif not isinstance(code, bytes):
        raise ValueError("code must be a string")

    # init Lua runtime
    L = luaL_newstate()
    if not L:
        raise MemoryError()

    try:
        # compile Lua code
        if luaL_loadbuffer(L, code, len(code), '<python>'):
            raise SyntaxError()

        # execute code
        if lua_pcall(L, 0, LUA_MULTRET, 0):
            raise RuntimeError()

        # convert return value (Lua number == float)
        assert lua_type(L, 1) == LUA_TNUMBER, "not a numeric return value"
        return lua_tonumber(L, 1)
    finally:
        lua_settop(L, 0)
        lua_close(L)
In [ ]:
code = '''
function fib(i)
    if i > 2 then
        return fib(i-1) + fib(i-2)
    else
        return 1
    end
end
'''

run_lua(code + "return fib(10)")
In [ ]:
%%timeit bench = code + "return fib(24)"

run_lua(bench)
In [ ]:
# distutils: include_dirs=/usr/include/luajit-2.0
# distutils: libraries=luajit-5.1

C arrays

In [ ]:
%%cython -a
def carrays():
    cdef int[10] a, b
    a[:5] = [1,2,3,4,5]
    b = a
    b[5:] = [6,7,8,9,10]

    for i in b[:3]:
        print(i+1)

    return b
In [ ]:
carrays()

C tuples

In [ ]:
%%cython -a

def ctuples():
    cdef int a = 42
    cdef double b = a / 5.0

    cdef (int, double) t = (a, b)
    print(t[0])
    print(t[1])
    print(t)

    return t
In [ ]:
ctuples()

C++

In [ ]:
%%cython -a
# distutils: language=c++


from libcpp.vector cimport vector

def func():
    cdef vector[int] v
    v.push_back(10)
    return v
In [ ]:
func()
In [ ]:
%%cython -a
# distutils: language=c++

from libcpp.vector cimport vector

cdef class Integers:
    cdef vector[int] _values

    def add(self, int value):
        self._values.push_back(value)

    def __repr__(self):
        return repr(self._values)
In [ ]:
x = Integers()
x.add(2)
x.add(3)
x