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.py
file 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.pxd
files. 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 codeimportcythonifcython.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.py
file.
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
,longlong
as 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
,long
andbint
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_typing
compiler 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 as
List[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.
Feature | Cython 0.29 | Cython 3.0 |
---|---|---|
| Any Python object | Exact Python |
| C | |
Builtin type e.g. | Exact type (no subclasses), not | |
Extension type defined in Cython | Specified type or a subclasses, not | |
| Equivalent C numeric type | |
| Not supported | Specified type (which must be a Python object), allows |
| Not supported | Specified type (which must be a Python object), allows |
| Not supported | Specified type (which must be a Python object), allows |
| Not supported | Exact |
| 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_sin
to 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.