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.ufunc
@cython.cfunc
def add_one(x: cython.double) -> cython.double:
    # 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.ufunc
@cython.cfunc
def add_one_add_two(x: cython.int) -> tuple[cython.int, cython.int]:
    return x+1, x+2

If you want to accept multiple different argument types then you can use Fused Types (Templates):

import cython

@cython.ufunc
@cython.cfunc
def generic_add_one(x: cython.numeric) -> cython.numeric:
    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.