Pure Python Mode

In some cases, it’s desirable to speed up Python code without losing theability to run it with the Python interpreter. While pure Python scriptscan be compiled with Cython, it usually results only in a speed gain ofabout 20%-50%.

To go beyond that, Cython provides language constructs to add static typingand cythonic functionalities to a Python module to make it run much fasterwhen compiled, while still allowing it to be interpreted.This is accomplished via an augmenting.pxd file, via PythontypePEP-484 type annotations (followingPEP 484 andPEP 526), and/orvia special functions and decorators available after importing the magiccython module. All three ways can be combined at need, althoughprojects would commonly decide on a specific way to keep the static typeinformation easy to manage.

Although it is not typically recommended over writing straight Cython codein a.pyx file, there are legitimate reasons to do this - easiertesting and debugging, collaboration with pure Python developers, etc.In pure mode, you are more or less restricted to code that can be expressed(or at least emulated) in Python, plus static type declarations. Anythingbeyond that can only be done in .pyx files with extended language syntax,because it depends on features of the Cython compiler.

Augmenting .pxd

Using an augmenting.pxd allows to let the original.py filecompletely untouched. On the other hand, one needs to maintain both the.pxd and the.py to keep them in sync.

While declarations in a.pyx file must correspond exactly with thoseof a.pxd file with the same name (and any contradiction results ina compile time error, seepxd files), the untyped definitions in a.py file can be overridden and augmented with static types by the morespecific ones present in a.pxd.

If a.pxd file is found with the same name as the.py filebeing compiled, it will be searched forcdef classes andcdef/cpdef functions and methods. The compiler willthen convert the corresponding classes/functions/methods in the.pyfile to be of the declared type. Thus if one has a fileA.py:

defmyfunction(x,y=2):a=x-yreturna+x*ydef_helper(a):returna+1classA:def__init__(self,b=0):self.a=3self.b=bdeffoo(self,x):print(x+_helper(1.0))

and addsA.pxd:

cpdefintmyfunction(intx,inty=*)cdefdouble_helper(doublea)cdefclassA:cdefpublicinta,bcpdeffoo(self,doublex)

then Cython will compile theA.py as if it had been written as follows:

cpdefintmyfunction(intx,inty=2):a=x-yreturna+x*ycdefdouble_helper(doublea):returna+1cdefclassA:cdefpublicinta,bdef__init__(self,b=0):self.a=3self.b=bcpdeffoo(self,doublex):print(x+_helper(1.0))

Notice how in order to provide the Python wrappers to the definitionsin the.pxd, that is, to be accessible from Python,

  • Python visible function signatures must be declared ascpdef (with defaultarguments replaced by a* to avoid repetition):

    cpdefintmyfunction(intx,inty=*)
  • C function signatures of internal functions can be declared ascdef:

    cdefdouble_helper(doublea)
  • cdef classes (extension types) are declared ascdef class;

  • cdef class attributes must be declared ascdef public if read/writePython access is needed,cdef readonly for read-only Python access, orplaincdef for internal C level attributes;

  • cdef class methods must be declared ascpdef for Python visiblemethods orcdef for internal C methods.

In the example above, the type of the local variablea inmyfunction()is not fixed and will thus be aPython object. To statically type it, onecan use Cython’s@cython.locals decorator (seeMagic Attributes,andMagic Attributes within the .pxd).

Normal Python (def) functions cannot be declared in.pxdfiles. It is therefore currently impossible to override the types of plainPython functions in.pxd files, e.g. to override types of their localvariables. In most cases, declaring them ascpdef will work as expected.

Magic Attributes

Special decorators are available from the magiccython module that canbe used to add static typing within the Python file, while being ignoredby the interpreter.

This option adds thecython module dependency to the original code, butdoes not require to maintain a supplementary.pxd file. Cythonprovides a fake version of this module asCython.Shadow, which is availableascython.py when Cython is installed, but can be copied to be used by othermodules when Cython is not installed.

“Compiled” switch

  • compiled is a special variable which is set toTrue when the compilerruns, andFalse in the interpreter. Thus, the code

    importcythonifcython.compiled:print("Yep, I'm compiled.")else:print("Just a lowly interpreted script.")

    will behave differently depending on whether or not the code is executed as acompiled extension (.so/.pyd) module or a plain.pyfile.

Static typing

  • cython.declare declares a typed variable in the current scope, which can beused in place of thecdeftypevar[=value] construct. This has two forms,the first as an assignment (useful as it creates a declaration in interpretedmode as well):

    importcythonx=cython.declare(cython.int)# cdef int xy=cython.declare(cython.double,0.57721)# cdef double y = 0.57721

    and the second mode as a simple function call:

    importcythoncython.declare(x=cython.int,y=cython.double)# cdef int x; cdef double y

    It can also be used to define extension type private, readonly and public attributes:

    importcython@cython.cclassclassA:cython.declare(a=cython.int,b=cython.int)c=cython.declare(cython.int,visibility='public')d=cython.declare(cython.int)# private by default.e=cython.declare(cython.int,visibility='readonly')def__init__(self,a,b,c,d=5,e=3):self.a=aself.b=bself.c=cself.d=dself.e=e
  • @cython.locals is a decorator that is used to specify the types of localvariables in the function body (including the arguments):

    importcython@cython.locals(a=cython.long,b=cython.long,n=cython.longlong)deffoo(a,b,x,y):n=a*b# ...
  • @cython.returns(<type>) specifies the function’s return type.

  • @cython.exceptval(value=None,*,check=False) specifies the function’s exceptionreturn value and exception check semantics as follows:

    @exceptval(-1)# cdef int func() except -1:@exceptval(-1,check=False)# cdef int func() except -1:@exceptval(check=True)# cdef int func() except *:@exceptval(-1,check=True)# cdef int func() except? -1:@exceptval(check=False)# no exception checking/propagation

    If exception propagation is disabled, any Python exceptions that are raisedinside of the function will be printed and ignored.

C types

There are numerous types built into the Cython module. It provides all thestandard C types, namelychar,short,int,long,longlongas well as their unsigned versionsuchar,ushort,uint,ulong,ulonglong. The specialbint type is used for C boolean values andPy_ssize_t for (signed) sizes of Python containers.

For each type, there are pointer typesp_int,pp_int, etc., up tothree levels deep in interpreted mode, and infinitely deep in compiled mode.Further pointer types can be constructed withcython.pointer[cython.int](orcython.pointer(cython.int) for compatibility with Cython versions before 3.1),and arrays ascython.int[10]. A limited attempt is made to emulate thesemore complex types, but only so much can be done from the Python language.

The Python types int, long and bool are interpreted as Cint,longandbint respectively. Also, the Python builtin typeslist,dict,tuple, etc. may be used, as well as any user defined types.

Typed C-tuples can be declared as a tuple of C types.

Extension types and cdef functions

  • The class decorator@cython.cclass creates acdefclass.

  • The function/method decorator@cython.cfunc creates acdef function.

  • @cython.ccall creates acpdef function, i.e. one that Cython codecan call at the C level.

  • @cython.locals declares local variables (see above). It can also be used todeclare types for arguments, i.e. the local variables that are used in thesignature.

  • @cython.inline is the equivalent of the Cinline modifier.

  • @cython.final terminates the inheritance chain by preventing a type frombeing used as a base class, or a method from being overridden in subtypes.This enables certain optimisations such as inlined method calls.

Here is an example of acdef function:

@cython.cfunc@cython.returns(cython.bint)@cython.locals(a=cython.int,b=cython.int)defc_compare(a,b):returna==b

Managing the Global Interpreter Lock

  • cython.nogil can be used as a context manager or as a decorator to replace thenogil keyword:

    withcython.nogil:# code block with the GIL released@cython.nogil@cython.cfuncdeffunc_released_gil()->cython.int:# function that can be run with the GIL released

    Note that the two uses differ: the context manager releases the GIL while the decorator marks that afunctioncan be run without the GIL. SeeCython and the GIL for more details.

  • cython.gil can be used as a context manager to replace thegil keyword:

    withcython.gil:# code block with the GIL acquired

    Note

    Cython currently does not support the@cython.with_gil decorator.

Both directives accept an optional boolean parameter for conditionallyreleasing or acquiring the GIL. The condition must be constant (at compile time):

withcython.nogil(False):# code block with the GIL not released@cython.nogil(True)@cython.cfuncdeffunc_released_gil()->cython.int:# function with the GIL releasedwithcython.gil(False):# code block with the GIL not acquiredwithcython.gil(True):# code block with the GIL acquired

A common use case for conditionally acquiring and releasing the GIL are fused typesthat allow different GIL handling depending on the specific type (seeConditional Acquiring / Releasing the GIL).

cimports

The specialcython.cimports package name gives access to cimportsin code that uses Python syntax. Note that this does not mean that Clibraries become available to Python code. It only means that you cantell Cython what cimports you want to use, without requiring specialsyntax. Running such code in plain Python will fail.

fromcython.cimports.libcimportmathdefuse_libc_math():returnmath.ceil(5.5)

Since such code must necessarily refer to the non-existingcython.cimports ‘package’, the plain cimport formcimportcython.cimports... is not available.You must use the formfromcython.cimports....

Further Cython functions and declarations

  • address is used in place of the& operator:

    cython.declare(x=cython.int,x_ptr=cython.p_int)x_ptr=cython.address(x)
  • sizeof emulates thesizeof operator. It can take both types andexpressions.

    cython.declare(n=cython.longlong)print(cython.sizeof(cython.longlong))print(cython.sizeof(n))
  • typeof returns a string representation of the argument’s type for debugging purposes. It can take expressions.

    cython.declare(n=cython.longlong)print(cython.typeof(n))
  • struct can be used to create struct types.:

    MyStruct=cython.struct(x=cython.int,y=cython.int,data=cython.double)a=cython.declare(MyStruct)

    is equivalent to the code:

    cdefstructMyStruct:intxintydoubledatacdefMyStructa
  • union creates union types with exactly the same syntax asstruct.

  • typedef defines a type under a given name:

    T=cython.typedef(cython.p_int)# ctypedef int* T
  • cast will (unsafely) reinterpret an expression type.cython.cast(T,t)is equivalent to<T>t. The first attribute must be a type, the second isthe expression to cast. Specifying the optional keyword argumenttypecheck=True has the semantics of<T?>t.

    t1=cython.cast(T,t)t2=cython.cast(T,t,typecheck=True)
  • fused_type creates a new type definition that refers to the multiple types.The following example declares a new type calledmy_fused_type which canbe either anint or adouble.:

    my_fused_type=cython.fused_type(cython.int,cython.float)

Magic Attributes within the .pxd

The specialcython module can also be imported and used within the augmenting.pxd file. For example, the following Python filedostuff.py:

defdostuff(n):t=0foriinrange(n):t+=ireturnt

can be augmented with the following.pxd filedostuff.pxd:

importcython@cython.locals(t=cython.int,i=cython.int)cpdefintdostuff(intn)

Thecython.declare() function can be used to specify types for globalvariables in the augmenting.pxd file.

PEP-484 type annotations

Pythontype hintscan be used to declare argument types, as shown in thefollowing example:

importcythondeffunc(foo:dict,bar:cython.int)->tuple:foo["hello world"]=3+barreturnfoo,5

Note the use ofcython.int rather thanint - Cython does not translateanint annotation to a C integer by default since the behaviour can bequite different with respect to overflow and division.

Annotations on global variables are currently ignored. This is because we expectannotation-typed code to be in majority written for Python, and global type annotationswould turn the Python variable into an internal C variable, thus removing it from themodule dict. To declare global variables as typed C variables, use@cython.declare().

Annotations can be combined with the@cython.exceptval() decorator for non-Pythonreturn types:

importcython@cython.exceptval(-1)deffunc(x:cython.int)->cython.int:ifx<0:raiseValueError("need integer >= 0")returnx+1

Note that the default exception handling behaviour when returning C numeric typesis to check for-1, and if that was returned, check Python’s error indicatorfor an exception. This means, if no@exceptval decorator is provided, and thereturn type is a numeric type, then the default with type annotations is@exceptval(-1,check=True), in order to make sure that exceptions are correctlyand efficiently reported to the caller. Exception propagation can be disabledexplicitly with@exceptval(check=False), in which case any Python exceptionsraised inside of the function will be printed and ignored.

Since version 0.27, Cython also supports the variable annotations definedinPEP 526. This allows todeclare types of variables in a Python 3.6 compatible way as follows:

importcythondeffunc():# Cython types are evaluated as for cdef declarationsx:cython.int# cdef int xy:cython.double=0.57721# cdef double y = 0.57721z:cython.float=0.57721# cdef float z  = 0.57721# Python types shadow Cython types for compatibility reasonsa:float=0.54321# cdef double a = 0.54321b:int=5# cdef object b = 5c:long=6# cdef object c = 6pass@cython.cclassclassA:a:cython.intb:cython.intdef__init__(self,b=0):self.a=3self.b=b

There is currently no way to express the visibility of object attributes.

Disabling annotations

To avoid conflicts with other kinds of annotationusages, Cython’s use of annotations to specify types can be disabled with theannotation_typingcompiler directive. From Cython 3you can use this as a decorator or a with statement, as shown in the following example:

importcython@cython.annotation_typing(False)deffunction_without_typing(a:int,b:int)->int:"""Cython is ignoring annotations in this function"""c:int=a+breturnc*a@cython.annotation_typing(False)@cython.cclassclassNotAnnotatedClass:"""Cython is ignoring annotatons in this class except annotated_method"""d:dictdef__init__(self,dictionary:dict):self.d=dictionary@cython.annotation_typing(True)defannotated_method(self,key:str,a:cython.int,b:cython.int):prefixed_key:str='prefix_'+keyself.d[prefixed_key]=a+bdefannotated_function(a:cython.int,b:cython.int):s:cython.int=a+bwithcython.annotation_typing(False):# Cython is ignoring annotations within this code blockc:list=[]c.append(a)c.append(b)c.append(s)returnc

typing Module

Support for the full range of annotations described by PEP-484 is not yetcomplete. Cython 3 currently understands the following features from thetyping module:

  • Optional[tp], which is interpreted astporNone;

  • Union[tp,None] orUnion[None,tp], which is interpreted astporNone;

  • typed containers such asList[str], which is interpreted aslist. Thehint that the elements are of typestr is currently ignored;

  • Tuple[...], which is converted into a Cython C-tuple where possibleand a regular Pythontuple otherwise.

  • ClassVar[...], which is understood in the context ofcdefclass or@cython.cclass.

Some of the unsupported features are likely to remainunsupported since these type hints are not relevant for the compilation toefficient C code. In other cases, however, where the generated C code couldbenefit from these type hints but does not currently, help is welcome toimprove the type analysis in Cython.

Reference table

The following reference table documents how type annotations are currently interpreted.Cython 0.29 behaviour is only shown where it differs from Cython 3.0 behaviour.The current limitations will likely be lifted at some point.

Annotation typing rules

Feature

Cython 0.29

Cython 3.0

int

Any Python object

Exact Pythonint (language_level=3 only)

float

Cdouble

Builtin type e.g.dict,list

Exact type (no subclasses), notNone

Extension type defined in Cython

Specified type or a subclasses, notNone

cython.int,cython.long, etc.

Equivalent C numeric type

typing.Optional[any_type]

Not supported

Specified type (which must be a Python object), allowsNone

typing.Union[any_type,None]

Not supported

Specified type (which must be a Python object), allowsNone

any_type|None

Not supported

Specified type (which must be a Python object), allowsNone

typing.List[any_type] (and similar)

Not supported

Exactlist, with the element type ignored currently

typing.ClassVar[...]

Not supported

Python-object class variable (when used in a class definition)

Tips and Tricks

Avoiding thecython runtime dependency

Python modules that are intended to run both compiled and as plain Pythonusually have the lineìmportcython in them and make use of the magicattributes in that module. If not compiled, this creates a runtime dependencyon Cython’s shadow module that provides fake implementations of types anddecorators.

Code that does not want to require Cython or its shadow module as as runtimedependency at all can often get away with a simple, stripped-down replacementlike the following:

try:importcythonexceptImportError:class_fake_cython:compiled=Falsedefcfunc(self,func):returnfuncdefccall(self,func):returnfuncdef__getattr__(self,type_name):return"object"cython=_fake_cython()

Calling C functions

The magiccython.cimports package provides a way to cimport externalcompile time C declarations from code written in plain Python. For convenience,it also provides a fallback Python implementation for thelibc.math module.

However, it is normally not possible tocall C functions in pure Pythoncode as there is no general way to represent them in normal (uncompiled) Python.But in cases where an equivalent Python function exists, this can be achievedby combining C function coercion with a conditional import as follows:

# mymodule.pxd# declare a C function as "cpdef" to export it to the modulecdefexternfrom"math.h":cpdefdoublesin(doublex)
# mymodule.pyimportcython# override with Python import if not in compiled codeifnotcython.compiled:frommathimportsin# calls sin() from math.h when compiled with Cython and math.sin() in Pythonprint(sin(0))

Note that the “sin” function will show up in the module namespace of “mymodule”here (i.e. there will be amymodule.sin() function). You can mark it as aninternal name according to Python conventions by renaming it to “_sin” in the.pxd file as follows:

cdefexternfrom"math.h":cpdefdouble_sin"sin"(doublex)

You would then also change the Python import tofrommathimportsinas_sinto make the names match again.

Using C arrays for fixed size lists

C arrays can automatically coerce to Python lists or tuples.This can be exploited to replace fixed size Python lists in Python code by Carrays when compiled. An example:

importcython@cython.locals(counts=cython.int[10],digit=cython.int)defcount_digits(digits):"""    >>> digits = '01112222333334445667788899'    >>> count_digits(map(int, digits))    [1, 3, 4, 5, 3, 1, 2, 2, 3, 2]    """counts=[0]*10fordigitindigits:assert0<=digit<=9counts[digit]+=1returncounts

In normal Python, this will use a Python list to collect the counts, whereasCython will generate C code that uses a C array of C ints.