Creating Numpy ufuncs¶
Note
This page uses two different syntax variants:
Cython specific
cdef
syntax, which was designed to make type declarations concise and easily readable from a C/C++ perspective.Pure Python syntax which allows static Cython type declarations in pure Python code, following PEP-484 type hints and PEP 526 variable annotations.
To make use of C data types in Python syntax, you need to import the special
cython
module in the Python module that you want to compile, e.g.import cython
If you use the pure Python syntax we strongly recommend you use a recent Cython 3 release, since significant improvements have been made here compared to the 0.29.x releases.
Numpy supports a special type of function called a ufunc . These support array broadcasting (i.e. the ability to handle arguments with any number of dimensions), alongside other useful features.
Cython can generate a ufunc from a Cython C function by tagging it with the @cython.ufunc
decorator. The input and output argument types should be scalar variables (“generic ufuncs” are
not yet supported) and should either by Python objects or simple numeric types. The body
of such a function is inserted into an efficient, compiled loop.
import cython
@cython.cfunc
@cython.ufunc
def add_one(x: cython.double) -> cython.double:
# of course, this simple operation can already by done efficiently in Numpy!
return x+1
cimport cython
@cython.ufunc
cdef double add_one(double x):
# of course, this simple operation can already by done efficiently in Numpy!
return x+1
You can have as many arguments to your function as you like. If you want to have multiple output arguments then you can use the ctuple syntax:
import cython
@cython.cfunc
@cython.ufunc
def add_one_add_two(x: cython.int) -> (cython.int, cython.int):
return x+1, x+2
cimport cython
@cython.ufunc
cdef (int, int) add_one_add_two(int x):
return x+1, x+2
If you want to accept multiple different argument types then you can use Fused Types (Templates):
import cython
@cython.cfunc
@cython.ufunc
def generic_add_one(x: cython.numeric) -> cython.numeric:
return x+1
cimport cython
@cython.ufunc
cdef cython.numeric generic_add_one(cython.numeric x):
return x+1
Finally, if you declare the cdef
/@cfunc
function as nogil
then Cython will release the
GIL once in the generated ufunc. This is a slight difference
from the general behaviour of nogil
functions (they generally do not automatically
release the GIL, but instead can be run without the GIL).
This feature relies on Numpy. Therefore if you create a ufunc in Cython, you must have the Numpy headers available when you build the generated C code, and users of your module must have Numpy installed when they run it.