CoCalc Public Filesadvanced-cython.htmlOpen with one click!
Author: Robert Bradshaw
Views : 1547
Description: Jupyter html version of advanced-cython.ipynb
Compute Environment: Ubuntu 18.04 (Deprecated)
advanced-cython
In [ ]:
%load_ext Cython 

This is a smattering of useful, but less widely known, features in


Cython directives

Cython has a huge number of directives documented at http://docs.cython.org/src/reference/compilation.html#compiler-directives These are like Python's __future__ directive in that they change the semantics of the language and control things like bounds checking and options like profiling and line tracing.

These can be set in the source file via #cython: directive=value or locally via with cython.directive(value) or in the setup.py file.

Unicode

Cython has very good support for unicode, including optimized .encode() and .decode() methods. Unfortuantly, lots of C libraries take raw char *. You can manually set the directives c_string_type and c_string_encoding to do conversions automatically.

In [ ]:
%%cython

cimport cython  # for directives

def default_behavior(x):
    s = x.encode('UTF-8')
    cdef char* c = s

#    cdef char* c = x  # if x is of type str, won't even compile
    return c
In [ ]:
default_behavior("abc")
In [ ]:
default_behavior(u"不好")
In [ ]:
%%cython

# cython: c_string_encoding=UTF-8
# cython: c_string_type=unicode

cimport cython  # for directives

def good_behavior(x):
    s = x.encode('UTF-8')
    cdef char* c = s
    return c
In [ ]:
print good_behavior(u"好")

Hints for wrapping C

Enums and functions can be declared cpdef to export them to Python.

In [ ]:
%%cython

cdef extern from "math.h":
    cdef  double cos(double)
    cpdef double sin(double)

cpdef enum Status:
    GOOD
    BAD
    UGLY
In [ ]:
cos(0)
In [ ]:
sin(0)
In [11]:
GOOD, BAD
Out[11]:
(0, 1)
In [12]:
%%cython

cdef extern from "math.h":
    cdef double cos(double)

def give_me_cos():
    return cos
In [13]:
give_me_cos()
Out[13]:
<cyfunction __Pyx_CFunc_double____double___to_py.<locals>.wrap at 0x7f9a3038a710>
In [14]:
give_me_cos()(0)
Out[14]:
1.0

Wrapping C++

Cython understands many stl containers, has automatic iteration and conversion.

In [15]:
%%cython --cplus

#distutils: language=c++

from libcpp.vector cimport vector

def use_vector(vector[double] x):
    for item in x:
        print "contains", item
    x[0] = 10
    return x
In [16]:
use_vector([1, 2, 3])
contains 1.0
contains 2.0
contains 3.0
Out[16]:
[10.0, 2.0, 3.0]

Use the new and del keyword for C++ object creation and deletion. Use x[0] or cython.operator.dereference for dereferencing.

In [17]:
%%cython --cplus

#distutils: language=c++

from libcpp.vector cimport vector
from cython.operator cimport dereference as deref

cdef vector[int] *x = new vector[int]()
x.push_back(3)
print x[0]
print deref(x)
del x
[3]
[3]

Use func[T] to declare template types.

In [18]:
%%cython --cplus

#distutils: language=c++

cdef extern from "<algorithm>" namespace "std":
    cdef T max[T](T, T)

print max(2, 4)
print max[double](2, 4)
4
4.0

Memory management

Leverage Cython memory management to manage C resources.

In [19]:
%%cython

from libc.stdlib cimport malloc, free

cdef class HoldMemory(object):
    cdef void* data
    def __cinit__(self, size):
        self.data = malloc(size)
    def __dealloc__(self):
        free(self.data)
In [20]:
%%cython --cplus

#distutils: language=c++

from libcpp.vector cimport vector

cdef class PyVector(object):
    cdef vector[int] c_vector
    def __init__(self, data):
        self.c_vector = data
    def __str__(self):
        return str(self.c_vector)

cdef class PyWrapper(object):
    cdef vector[int]* c_vector  # use this if there is not a no-arg constructor
    def __cinit__(self):
        self.c_vector = new vector[int]()
    def __dealloc__(self):
        del self.c_vector
In [22]:
print PyVector([1,2,3])
[1, 2, 3]

Structs and tuples

C structs can be defined using the cdef struct StructName:. Anonomous tuples can be created via (type, type, ...).

In [23]:
%%cython

cdef struct Point:
    double x
    double y
    double z

cdef Point p = Point(1, 2, z=3)
print p
p.x = 100
print p

cpdef (double, double) project(Point p):
    return p.x / p.z, p.y / p.z
{'y': 2.0, 'x': 1.0, 'z': 3.0}
{'y': 2.0, 'x': 100.0, 'z': 3.0}
In [24]:
project({'x': 10, 'y': 1, 'z': 100})
Out[24]:
(0.1, 0.01)

Fused types

Cython doesn't have templates (for that use an external templating engine), but does have a special construct called "fused types" that allows one to create several specializations for an enumeriated set of types.

In [25]:
%%cython

cdef fused MyType:
    long
    double
    str

def add_to_self(MyType a):
    return a+a
In [26]:
add_to_self(10)
Out[26]:
20
In [27]:
add_to_self(1.5)
Out[27]:
3.0
In [28]:
add_to_self("abc")
Out[28]:
'abcabc'
In [29]:
add_to_self['double'](2)
Out[29]:
4.0
In [30]:
add_to_self([1,2,3])
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-30-823daff39d5a> in <module>()
----> 1 add_to_self([1,2,3])

_cython_magic_00099e8caeb800914f62d34c4890e107.pyx in _cython_magic_00099e8caeb800914f62d34c4890e107.__pyx_fused_cpdef (/projects/d572c32f-0b22-4b4f-8a67-fcbd8e10a9f6/.sage/ipython-3.1.0/cython/_cython_magic_00099e8caeb800914f62d34c4890e107.c:1213)()

TypeError: No matching signature found
In [ ]:
 

Avoiding the GIL

The GIL, or global interpreter lock, must be held for all CPython interactions, however we can release it for pure C code to incrase concurrency. Without the GIL, you can only call code that also does not require the GIL and is explicitly marked as such.

In [31]:
%%cython

def release_gil(int x):
    cdef int y
    with nogil:
        y = x*x
        with gil:
            print "Got", y
        y = no_gil_required(y)
        aquites_gil(y)
    return y


cdef int no_gil_required(int a) nogil:
    return 2 * a

cdef void aquites_gil(int a) with gil:
    print "a", a
In [32]:
release_gil(100)
Got 10000
a 20000
Out[32]:
20000

Type inference

In [33]:
%%cython

import cython

@cython.infer_types(None)
def default_inference(int x, double y):
    a = x
    a += 1
    print cython.typeof(a)
    b = y
    b += 1
    print cython.typeof(b)

@cython.infer_types(False)
def no_inference(int x, double y):
    a = x
    a += 1
    print cython.typeof(a)
    b = y
    b += 1
    print cython.typeof(b)

@cython.infer_types(True)
def unsafe_inference(int x, double y):
    a = x
    a += 1
    print cython.typeof(a)
    b = y
    b += 1
    print cython.typeof(b)
In [34]:
default_inference(1, 1.0)
Python object
double
In [35]:
no_inference(1, 1.0)
Python object
Python object
In [36]:
unsafe_inference(1, 1.0)
long
double
In [ ]:
 
In [47]:
%%cython

cdef extern from "math.h":
    cpdef double foo "sin" (double)
    cdef double foo2 "sin(3)"
    ctypedef int my_int "int"

ctypedef double blah
cdef blah my_var = 4.5

print foo(3.14)
print foo2
0.00159265291649
0.14112000806
In [43]:
foo(100)
Out[43]:
-0.5063656411097588
In [ ]: