Blame docs/src/tutorial/clibraries.rst

Packit Service 99d393
Using C libraries
Packit Service 99d393
=================
Packit Service 99d393
Packit Service 99d393
Apart from writing fast code, one of the main use cases of Cython is
Packit Service 99d393
to call external C libraries from Python code.  As Cython code
Packit Service 99d393
compiles down to C code itself, it is actually trivial to call C
Packit Service 99d393
functions directly in the code.  The following gives a complete
Packit Service 99d393
example for using (and wrapping) an external C library in Cython code,
Packit Service 99d393
including appropriate error handling and considerations about
Packit Service 99d393
designing a suitable API for Python and Cython code.
Packit Service 99d393
Packit Service 99d393
Imagine you need an efficient way to store integer values in a FIFO
Packit Service 99d393
queue.  Since memory really matters, and the values are actually
Packit Service 99d393
coming from C code, you cannot afford to create and store Python
Packit Service 99d393
``int`` objects in a list or deque.  So you look out for a queue
Packit Service 99d393
implementation in C.
Packit Service 99d393
Packit Service 99d393
After some web search, you find the C-algorithms library [CAlg]_ and
Packit Service 99d393
decide to use its double ended queue implementation.  To make the
Packit Service 99d393
handling easier, however, you decide to wrap it in a Python extension
Packit Service 99d393
type that can encapsulate all memory management.
Packit Service 99d393
Packit Service 99d393
.. [CAlg] Simon Howard, C Algorithms library, http://c-algorithms.sourceforge.net/
Packit Service 99d393
Packit Service 99d393
Packit Service 99d393
Defining external declarations
Packit Service 99d393
------------------------------
Packit Service 99d393
Packit Service 99d393
The C API of the queue implementation, which is defined in the header
Packit Service 99d393
file ``libcalg/queue.h``, essentially looks like this::
Packit Service 99d393
Packit Service 99d393
    /* file: queue.h */
Packit Service 99d393
Packit Service 99d393
    typedef struct _Queue Queue;
Packit Service 99d393
    typedef void *QueueValue;
Packit Service 99d393
Packit Service 99d393
    Queue *queue_new(void);
Packit Service 99d393
    void queue_free(Queue *queue);
Packit Service 99d393
Packit Service 99d393
    int queue_push_head(Queue *queue, QueueValue data);
Packit Service 99d393
    QueueValue queue_pop_head(Queue *queue);
Packit Service 99d393
    QueueValue queue_peek_head(Queue *queue);
Packit Service 99d393
Packit Service 99d393
    int queue_push_tail(Queue *queue, QueueValue data);
Packit Service 99d393
    QueueValue queue_pop_tail(Queue *queue);
Packit Service 99d393
    QueueValue queue_peek_tail(Queue *queue);
Packit Service 99d393
Packit Service 99d393
    int queue_is_empty(Queue *queue);
Packit Service 99d393
Packit Service 99d393
To get started, the first step is to redefine the C API in a ``.pxd``
Packit Service 99d393
file, say, ``cqueue.pxd``::
Packit Service 99d393
Packit Service 99d393
    # file: cqueue.pxd
Packit Service 99d393
Packit Service 99d393
    cdef extern from "libcalg/queue.h":
Packit Service 99d393
        ctypedef struct Queue:
Packit Service 99d393
            pass
Packit Service 99d393
        ctypedef void* QueueValue
Packit Service 99d393
Packit Service 99d393
        Queue* queue_new()
Packit Service 99d393
        void queue_free(Queue* queue)
Packit Service 99d393
Packit Service 99d393
        int queue_push_head(Queue* queue, QueueValue data)
Packit Service 99d393
        QueueValue  queue_pop_head(Queue* queue)
Packit Service 99d393
        QueueValue queue_peek_head(Queue* queue)
Packit Service 99d393
Packit Service 99d393
        int queue_push_tail(Queue* queue, QueueValue data)
Packit Service 99d393
        QueueValue queue_pop_tail(Queue* queue)
Packit Service 99d393
        QueueValue queue_peek_tail(Queue* queue)
Packit Service 99d393
Packit Service 99d393
        bint queue_is_empty(Queue* queue)
Packit Service 99d393
Packit Service 99d393
Note how these declarations are almost identical to the header file
Packit Service 99d393
declarations, so you can often just copy them over.  However, you do
Packit Service 99d393
not need to provide *all* declarations as above, just those that you
Packit Service 99d393
use in your code or in other declarations, so that Cython gets to see
Packit Service 99d393
a sufficient and consistent subset of them.  Then, consider adapting
Packit Service 99d393
them somewhat to make them more comfortable to work with in Cython.
Packit Service 99d393
Packit Service 99d393
Specifically, you should take care of choosing good argument names
Packit Service 99d393
for the C functions, as Cython allows you to pass them as keyword
Packit Service 99d393
arguments.  Changing them later on is a backwards incompatible API
Packit Service 99d393
modification.  Choosing good names right away will make these
Packit Service 99d393
functions more pleasant to work with from Cython code.
Packit Service 99d393
Packit Service 99d393
One noteworthy difference to the header file that we use above is the
Packit Service 99d393
declaration of the ``Queue`` struct in the first line.  ``Queue`` is
Packit Service 99d393
in this case used as an *opaque handle*; only the library that is
Packit Service 99d393
called knows what is really inside.  Since no Cython code needs to
Packit Service 99d393
know the contents of the struct, we do not need to declare its
Packit Service 99d393
contents, so we simply provide an empty definition (as we do not want
Packit Service 99d393
to declare the ``_Queue`` type which is referenced in the C header)
Packit Service 99d393
[#]_.
Packit Service 99d393
Packit Service 99d393
.. [#] There's a subtle difference between ``cdef struct Queue: pass``
Packit Service 99d393
       and ``ctypedef struct Queue: pass``.  The former declares a
Packit Service 99d393
       type which is referenced in C code as ``struct Queue``, while
Packit Service 99d393
       the latter is referenced in C as ``Queue``.  This is a C
Packit Service 99d393
       language quirk that Cython is not able to hide.  Most modern C
Packit Service 99d393
       libraries use the ``ctypedef`` kind of struct.
Packit Service 99d393
Packit Service 99d393
Another exception is the last line.  The integer return value of the
Packit Service 99d393
``queue_is_empty()`` function is actually a C boolean value, i.e. the
Packit Service 99d393
only interesting thing about it is whether it is non-zero or zero,
Packit Service 99d393
indicating if the queue is empty or not.  This is best expressed by
Packit Service 99d393
Cython's ``bint`` type, which is a normal ``int`` type when used in C
Packit Service 99d393
but maps to Python's boolean values ``True`` and ``False`` when
Packit Service 99d393
converted to a Python object.  This way of tightening declarations in
Packit Service 99d393
a ``.pxd`` file can often simplify the code that uses them.
Packit Service 99d393
Packit Service 99d393
It is good practice to define one ``.pxd`` file for each library that
Packit Service 99d393
you use, and sometimes even for each header file (or functional group)
Packit Service 99d393
if the API is large.  That simplifies their reuse in other projects.
Packit Service 99d393
Sometimes, you may need to use C functions from the standard C
Packit Service 99d393
library, or want to call C-API functions from CPython directly.  For
Packit Service 99d393
common needs like this, Cython ships with a set of standard ``.pxd``
Packit Service 99d393
files that provide these declarations in a readily usable way that is
Packit Service 99d393
adapted to their use in Cython.  The main packages are ``cpython``,
Packit Service 99d393
``libc`` and ``libcpp``.  The NumPy library also has a standard
Packit Service 99d393
``.pxd`` file ``numpy``, as it is often used in Cython code.  See
Packit Service 99d393
Cython's ``Cython/Includes/`` source package for a complete list of
Packit Service 99d393
provided ``.pxd`` files.
Packit Service 99d393
Packit Service 99d393
Packit Service 99d393
Writing a wrapper class
Packit Service 99d393
-----------------------
Packit Service 99d393
Packit Service 99d393
After declaring our C library's API, we can start to design the Queue
Packit Service 99d393
class that should wrap the C queue.  It will live in a file called
Packit Service 99d393
``queue.pyx``. [#]_
Packit Service 99d393
Packit Service 99d393
.. [#] Note that the name of the ``.pyx`` file must be different from
Packit Service 99d393
       the ``cqueue.pxd`` file with declarations from the C library,
Packit Service 99d393
       as both do not describe the same code.  A ``.pxd`` file next to
Packit Service 99d393
       a ``.pyx`` file with the same name defines exported
Packit Service 99d393
       declarations for code in the ``.pyx`` file.  As the
Packit Service 99d393
       ``cqueue.pxd`` file contains declarations of a regular C
Packit Service 99d393
       library, there must not be a ``.pyx`` file with the same name
Packit Service 99d393
       that Cython associates with it.
Packit Service 99d393
Packit Service 99d393
Here is a first start for the Queue class::
Packit Service 99d393
Packit Service 99d393
    # file: queue.pyx
Packit Service 99d393
Packit Service 99d393
    cimport cqueue
Packit Service 99d393
Packit Service 99d393
    cdef class Queue:
Packit Service 99d393
        cdef cqueue.Queue* _c_queue
Packit Service 99d393
        def __cinit__(self):
Packit Service 99d393
            self._c_queue = cqueue.queue_new()
Packit Service 99d393
Packit Service 99d393
Note that it says ``__cinit__`` rather than ``__init__``.  While
Packit Service 99d393
``__init__`` is available as well, it is not guaranteed to be run (for
Packit Service 99d393
instance, one could create a subclass and forget to call the
Packit Service 99d393
ancestor's constructor).  Because not initializing C pointers often
Packit Service 99d393
leads to hard crashes of the Python interpreter, Cython provides
Packit Service 99d393
``__cinit__`` which is *always* called immediately on construction,
Packit Service 99d393
before CPython even considers calling ``__init__``, and which
Packit Service 99d393
therefore is the right place to initialise ``cdef`` fields of the new
Packit Service 99d393
instance.  However, as ``__cinit__`` is called during object
Packit Service 99d393
construction, ``self`` is not fully constructed yet, and one must
Packit Service 99d393
avoid doing anything with ``self`` but assigning to ``cdef`` fields.
Packit Service 99d393
Packit Service 99d393
Note also that the above method takes no parameters, although subtypes
Packit Service 99d393
may want to accept some.  A no-arguments ``__cinit__()`` method is a
Packit Service 99d393
special case here that simply does not receive any parameters that
Packit Service 99d393
were passed to a constructor, so it does not prevent subclasses from
Packit Service 99d393
adding parameters.  If parameters are used in the signature of
Packit Service 99d393
``__cinit__()``, they must match those of any declared ``__init__``
Packit Service 99d393
method of classes in the class hierarchy that are used to instantiate
Packit Service 99d393
the type.
Packit Service 99d393
Packit Service 99d393
Packit Service 99d393
Memory management
Packit Service 99d393
-----------------
Packit Service 99d393
Packit Service 99d393
Before we continue implementing the other methods, it is important to
Packit Service 99d393
understand that the above implementation is not safe.  In case
Packit Service 99d393
anything goes wrong in the call to ``queue_new()``, this code will
Packit Service 99d393
simply swallow the error, so we will likely run into a crash later on.
Packit Service 99d393
According to the documentation of the ``queue_new()`` function, the
Packit Service 99d393
only reason why the above can fail is due to insufficient memory.  In
Packit Service 99d393
that case, it will return ``NULL``, whereas it would normally return a
Packit Service 99d393
pointer to the new queue.
Packit Service 99d393
Packit Service 99d393
The Python way to get out of this is to raise a ``MemoryError`` [#]_.
Packit Service 99d393
We can thus change the init function as follows::
Packit Service 99d393
Packit Service 99d393
    cimport cqueue
Packit Service 99d393
Packit Service 99d393
    cdef class Queue:
Packit Service 99d393
        cdef cqueue.Queue* _c_queue
Packit Service 99d393
        def __cinit__(self):
Packit Service 99d393
            self._c_queue = cqueue.queue_new()
Packit Service 99d393
            if self._c_queue is NULL:
Packit Service 99d393
                raise MemoryError()
Packit Service 99d393
Packit Service 99d393
.. [#] In the specific case of a ``MemoryError``, creating a new
Packit Service 99d393
   exception instance in order to raise it may actually fail because
Packit Service 99d393
   we are running out of memory.  Luckily, CPython provides a C-API
Packit Service 99d393
   function ``PyErr_NoMemory()`` that safely raises the right
Packit Service 99d393
   exception for us.  Since version 0.14.1, Cython automatically
Packit Service 99d393
   substitutes this C-API call whenever you write ``raise
Packit Service 99d393
   MemoryError`` or ``raise MemoryError()``.  If you use an older
Packit Service 99d393
   version, you have to cimport the C-API function from the standard
Packit Service 99d393
   package ``cpython.exc`` and call it directly.
Packit Service 99d393
Packit Service 99d393
The next thing to do is to clean up when the Queue instance is no
Packit Service 99d393
longer used (i.e. all references to it have been deleted).  To this
Packit Service 99d393
end, CPython provides a callback that Cython makes available as a
Packit Service 99d393
special method ``__dealloc__()``.  In our case, all we have to do is
Packit Service 99d393
to free the C Queue, but only if we succeeded in initialising it in
Packit Service 99d393
the init method::
Packit Service 99d393
Packit Service 99d393
        def __dealloc__(self):
Packit Service 99d393
            if self._c_queue is not NULL:
Packit Service 99d393
                cqueue.queue_free(self._c_queue)
Packit Service 99d393
Packit Service 99d393
Packit Service 99d393
Compiling and linking
Packit Service 99d393
---------------------
Packit Service 99d393
Packit Service 99d393
At this point, we have a working Cython module that we can test.  To
Packit Service 99d393
compile it, we need to configure a ``setup.py`` script for distutils.
Packit Service 99d393
Here is the most basic script for compiling a Cython module::
Packit Service 99d393
Packit Service 99d393
    from distutils.core import setup
Packit Service 99d393
    from distutils.extension import Extension
Packit Service 99d393
    from Cython.Build import cythonize
Packit Service 99d393
Packit Service 99d393
    setup(
Packit Service 99d393
        ext_modules = cythonize([Extension("queue", ["queue.pyx"])])
Packit Service 99d393
    )
Packit Service 99d393
Packit Service 99d393
To build against the external C library, we must extend this script to
Packit Service 99d393
include the necessary setup.  Assuming the library is installed in the
Packit Service 99d393
usual places (e.g. under ``/usr/lib`` and ``/usr/include`` on a
Packit Service 99d393
Unix-like system), we could simply change the extension setup from
Packit Service 99d393
Packit Service 99d393
::
Packit Service 99d393
Packit Service 99d393
    ext_modules = cythonize([Extension("queue", ["queue.pyx"])])
Packit Service 99d393
Packit Service 99d393
to
Packit Service 99d393
Packit Service 99d393
::
Packit Service 99d393
Packit Service 99d393
    ext_modules = cythonize([
Packit Service 99d393
        Extension("queue", ["queue.pyx"],
Packit Service 99d393
                  libraries=["calg"])
Packit Service 99d393
        ])
Packit Service 99d393
Packit Service 99d393
If it is not installed in a 'normal' location, users can provide the
Packit Service 99d393
required parameters externally by passing appropriate C compiler
Packit Service 99d393
flags, such as::
Packit Service 99d393
Packit Service 99d393
    CFLAGS="-I/usr/local/otherdir/calg/include"  \
Packit Service 99d393
    LDFLAGS="-L/usr/local/otherdir/calg/lib"     \
Packit Service 99d393
        python setup.py build_ext -i
Packit Service 99d393
Packit Service 99d393
Once we have compiled the module for the first time, we can now import
Packit Service 99d393
it and instantiate a new Queue::
Packit Service 99d393
Packit Service 99d393
    $ export PYTHONPATH=.
Packit Service 99d393
    $ python -c 'import queue.Queue as Q ; Q()'
Packit Service 99d393
Packit Service 99d393
However, this is all our Queue class can do so far, so let's make it
Packit Service 99d393
more usable.
Packit Service 99d393
Packit Service 99d393
Packit Service 99d393
Mapping functionality
Packit Service 99d393
---------------------
Packit Service 99d393
Packit Service 99d393
Before implementing the public interface of this class, it is good
Packit Service 99d393
practice to look at what interfaces Python offers, e.g. in its
Packit Service 99d393
``list`` or ``collections.deque`` classes.  Since we only need a FIFO
Packit Service 99d393
queue, it's enough to provide the methods ``append()``, ``peek()`` and
Packit Service 99d393
``pop()``, and additionally an ``extend()`` method to add multiple
Packit Service 99d393
values at once.  Also, since we already know that all values will be
Packit Service 99d393
coming from C, it's best to provide only ``cdef`` methods for now, and
Packit Service 99d393
to give them a straight C interface.
Packit Service 99d393
Packit Service 99d393
In C, it is common for data structures to store data as a ``void*`` to
Packit Service 99d393
whatever data item type.  Since we only want to store ``int`` values,
Packit Service 99d393
which usually fit into the size of a pointer type, we can avoid
Packit Service 99d393
additional memory allocations through a trick: we cast our ``int`` values
Packit Service 99d393
to ``void*`` and vice versa, and store the value directly as the
Packit Service 99d393
pointer value.
Packit Service 99d393
Packit Service 99d393
Here is a simple implementation for the ``append()`` method::
Packit Service 99d393
Packit Service 99d393
        cdef append(self, int value):
Packit Service 99d393
            cqueue.queue_push_tail(self._c_queue, <void*>value)
Packit Service 99d393
Packit Service 99d393
Again, the same error handling considerations as for the
Packit Service 99d393
``__cinit__()`` method apply, so that we end up with this
Packit Service 99d393
implementation instead::
Packit Service 99d393
Packit Service 99d393
        cdef append(self, int value):
Packit Service 99d393
            if not cqueue.queue_push_tail(self._c_queue,
Packit Service 99d393
                                          <void*>value):
Packit Service 99d393
                raise MemoryError()
Packit Service 99d393
Packit Service 99d393
Adding an ``extend()`` method should now be straight forward::
Packit Service 99d393
Packit Service 99d393
    cdef extend(self, int* values, size_t count):
Packit Service 99d393
        """Append all ints to the queue.
Packit Service 99d393
        """
Packit Service 99d393
        cdef size_t i
Packit Service 99d393
        for i in range(count):
Packit Service 99d393
            if not cqueue.queue_push_tail(
Packit Service 99d393
                    self._c_queue, <void*>values[i]):
Packit Service 99d393
                raise MemoryError()
Packit Service 99d393
Packit Service 99d393
This becomes handy when reading values from a NumPy array, for
Packit Service 99d393
example.
Packit Service 99d393
Packit Service 99d393
So far, we can only add data to the queue.  The next step is to write
Packit Service 99d393
the two methods to get the first element: ``peek()`` and ``pop()``,
Packit Service 99d393
which provide read-only and destructive read access respectively::
Packit Service 99d393
Packit Service 99d393
    cdef int peek(self):
Packit Service 99d393
        return <int>cqueue.queue_peek_head(self._c_queue)
Packit Service 99d393
Packit Service 99d393
    cdef int pop(self):
Packit Service 99d393
        return <int>cqueue.queue_pop_head(self._c_queue)
Packit Service 99d393
Packit Service 99d393
Packit Service 99d393
Handling errors
Packit Service 99d393
---------------
Packit Service 99d393
Packit Service 99d393
Now, what happens when the queue is empty?  According to the
Packit Service 99d393
documentation, the functions return a ``NULL`` pointer, which is
Packit Service 99d393
typically not a valid value.  Since we are simply casting to and
Packit Service 99d393
from ints, we cannot distinguish anymore if the return value was
Packit Service 99d393
``NULL`` because the queue was empty or because the value stored in
Packit Service 99d393
the queue was ``0``.  However, in Cython code, we would expect the
Packit Service 99d393
first case to raise an exception, whereas the second case should
Packit Service 99d393
simply return ``0``.  To deal with this, we need to special case this
Packit Service 99d393
value, and check if the queue really is empty or not::
Packit Service 99d393
Packit Service 99d393
    cdef int peek(self) except? -1:
Packit Service 99d393
        value = <int>cqueue.queue_peek_head(self._c_queue)
Packit Service 99d393
        if value == 0:
Packit Service 99d393
            # this may mean that the queue is empty, or
Packit Service 99d393
            # that it happens to contain a 0 value
Packit Service 99d393
            if cqueue.queue_is_empty(self._c_queue):
Packit Service 99d393
                raise IndexError("Queue is empty")
Packit Service 99d393
        return value
Packit Service 99d393
Packit Service 99d393
Note how we have effectively created a fast path through the method in
Packit Service 99d393
the hopefully common cases that the return value is not ``0``.  Only
Packit Service 99d393
that specific case needs an additional check if the queue is empty.
Packit Service 99d393
Packit Service 99d393
The ``except? -1`` declaration in the method signature falls into the
Packit Service 99d393
same category.  If the function was a Python function returning a
Packit Service 99d393
Python object value, CPython would simply return ``NULL`` internally
Packit Service 99d393
instead of a Python object to indicate an exception, which would
Packit Service 99d393
immediately be propagated by the surrounding code.  The problem is
Packit Service 99d393
that the return type is ``int`` and any ``int`` value is a valid queue
Packit Service 99d393
item value, so there is no way to explicitly signal an error to the
Packit Service 99d393
calling code.  In fact, without such a declaration, there is no
Packit Service 99d393
obvious way for Cython to know what to return on exceptions and for
Packit Service 99d393
calling code to even know that this method *may* exit with an
Packit Service 99d393
exception.
Packit Service 99d393
Packit Service 99d393
The only way calling code can deal with this situation is to call
Packit Service 99d393
``PyErr_Occurred()`` when returning from a function to check if an
Packit Service 99d393
exception was raised, and if so, propagate the exception.  This
Packit Service 99d393
obviously has a performance penalty.  Cython therefore allows you to
Packit Service 99d393
declare which value it should implicitly return in the case of an
Packit Service 99d393
exception, so that the surrounding code only needs to check for an
Packit Service 99d393
exception when receiving this exact value.
Packit Service 99d393
Packit Service 99d393
We chose to use ``-1`` as the exception return value as we expect it
Packit Service 99d393
to be an unlikely value to be put into the queue.  The question mark
Packit Service 99d393
in the ``except? -1`` declaration indicates that the return value is
Packit Service 99d393
ambiguous (there *may* be a ``-1`` value in the queue, after all) and
Packit Service 99d393
that an additional exception check using ``PyErr_Occurred()`` is
Packit Service 99d393
needed in calling code.  Without it, Cython code that calls this
Packit Service 99d393
method and receives the exception return value would silently (and
Packit Service 99d393
sometimes incorrectly) assume that an exception has been raised.  In
Packit Service 99d393
any case, all other return values will be passed through almost
Packit Service 99d393
without a penalty, thus again creating a fast path for 'normal'
Packit Service 99d393
values.
Packit Service 99d393
Packit Service 99d393
Now that the ``peek()`` method is implemented, the ``pop()`` method
Packit Service 99d393
also needs adaptation.  Since it removes a value from the queue,
Packit Service 99d393
however, it is not enough to test if the queue is empty *after* the
Packit Service 99d393
removal.  Instead, we must test it on entry::
Packit Service 99d393
Packit Service 99d393
    cdef int pop(self) except? -1:
Packit Service 99d393
        if cqueue.queue_is_empty(self._c_queue):
Packit Service 99d393
            raise IndexError("Queue is empty")
Packit Service 99d393
        return <int>cqueue.queue_pop_head(self._c_queue)
Packit Service 99d393
Packit Service 99d393
The return value for exception propagation is declared exactly as for
Packit Service 99d393
``peek()``.
Packit Service 99d393
Packit Service 99d393
Lastly, we can provide the Queue with an emptiness indicator in the
Packit Service 99d393
normal Python way by implementing the ``__bool__()`` special method
Packit Service 99d393
(note that Python 2 calls this method ``__nonzero__``, whereas Cython
Packit Service 99d393
code can use either name)::
Packit Service 99d393
Packit Service 99d393
    def __bool__(self):
Packit Service 99d393
        return not cqueue.queue_is_empty(self._c_queue)
Packit Service 99d393
Packit Service 99d393
Note that this method returns either ``True`` or ``False`` as we
Packit Service 99d393
declared the return type of the ``queue_is_empty()`` function as
Packit Service 99d393
``bint`` in ``cqueue.pxd``.
Packit Service 99d393
Packit Service 99d393
Packit Service 99d393
Testing the result
Packit Service 99d393
------------------
Packit Service 99d393
Packit Service 99d393
Now that the implementation is complete, you may want to write some
Packit Service 99d393
tests for it to make sure it works correctly.  Especially doctests are
Packit Service 99d393
very nice for this purpose, as they provide some documentation at the
Packit Service 99d393
same time.  To enable doctests, however, you need a Python API that
Packit Service 99d393
you can call.  C methods are not visible from Python code, and thus
Packit Service 99d393
not callable from doctests.
Packit Service 99d393
Packit Service 99d393
A quick way to provide a Python API for the class is to change the
Packit Service 99d393
methods from ``cdef`` to ``cpdef``.  This will let Cython generate two
Packit Service 99d393
entry points, one that is callable from normal Python code using the
Packit Service 99d393
Python call semantics and Python objects as arguments, and one that is
Packit Service 99d393
callable from C code with fast C semantics and without requiring
Packit Service 99d393
intermediate argument conversion from or to Python types. Note that ``cpdef``
Packit Service 99d393
methods ensure that they can be appropriately overridden by Python
Packit Service 99d393
methods even when they are called from Cython. This adds a tiny overhead
Packit Service 99d393
compared to ``cdef`` methods.
Packit Service 99d393
Packit Service 99d393
The following listing shows the complete implementation that uses
Packit Service 99d393
``cpdef`` methods where possible::
Packit Service 99d393
Packit Service 99d393
    cimport cqueue
Packit Service 99d393
Packit Service 99d393
    cdef class Queue:
Packit Service 99d393
        """A queue class for C integer values.
Packit Service 99d393
Packit Service 99d393
        >>> q = Queue()
Packit Service 99d393
        >>> q.append(5)
Packit Service 99d393
        >>> q.peek()
Packit Service 99d393
        5
Packit Service 99d393
        >>> q.pop()
Packit Service 99d393
        5
Packit Service 99d393
        """
Packit Service 99d393
        cdef cqueue.Queue* _c_queue
Packit Service 99d393
        def __cinit__(self):
Packit Service 99d393
            self._c_queue = cqueue.queue_new()
Packit Service 99d393
            if self._c_queue is NULL:
Packit Service 99d393
                raise MemoryError()
Packit Service 99d393
Packit Service 99d393
        def __dealloc__(self):
Packit Service 99d393
            if self._c_queue is not NULL:
Packit Service 99d393
                cqueue.queue_free(self._c_queue)
Packit Service 99d393
Packit Service 99d393
        cpdef append(self, int value):
Packit Service 99d393
            if not cqueue.queue_push_tail(self._c_queue,
Packit Service 99d393
                                          <void*>value):
Packit Service 99d393
                raise MemoryError()
Packit Service 99d393
Packit Service 99d393
        cdef extend(self, int* values, size_t count):
Packit Service 99d393
            cdef size_t i
Packit Service 99d393
            for i in xrange(count):
Packit Service 99d393
                if not cqueue.queue_push_tail(
Packit Service 99d393
                        self._c_queue, <void*>values[i]):
Packit Service 99d393
                    raise MemoryError()
Packit Service 99d393
Packit Service 99d393
        cpdef int peek(self) except? -1:
Packit Service 99d393
            cdef int value = \
Packit Service 99d393
                <int>cqueue.queue_peek_head(self._c_queue)
Packit Service 99d393
            if value == 0:
Packit Service 99d393
                # this may mean that the queue is empty,
Packit Service 99d393
                # or that it happens to contain a 0 value
Packit Service 99d393
                if cqueue.queue_is_empty(self._c_queue):
Packit Service 99d393
                    raise IndexError("Queue is empty")
Packit Service 99d393
            return value
Packit Service 99d393
Packit Service 99d393
        cpdef int pop(self) except? -1:
Packit Service 99d393
            if cqueue.queue_is_empty(self._c_queue):
Packit Service 99d393
                raise IndexError("Queue is empty")
Packit Service 99d393
            return <int>cqueue.queue_pop_head(self._c_queue)
Packit Service 99d393
Packit Service 99d393
        def __bool__(self):
Packit Service 99d393
            return not cqueue.queue_is_empty(self._c_queue)
Packit Service 99d393
Packit Service 99d393
The ``cpdef`` feature is obviously not available for the ``extend()``
Packit Service 99d393
method, as the method signature is incompatible with Python argument
Packit Service 99d393
types.  However, if wanted, we can rename the C-ish ``extend()``
Packit Service 99d393
method to e.g. ``c_extend()``, and write a new ``extend()`` method
Packit Service 99d393
instead that accepts an arbitrary Python iterable::
Packit Service 99d393
Packit Service 99d393
        cdef c_extend(self, int* values, size_t count):
Packit Service 99d393
            cdef size_t i
Packit Service 99d393
            for i in range(count):
Packit Service 99d393
                if not cqueue.queue_push_tail(
Packit Service 99d393
                        self._c_queue, <void*>values[i]):
Packit Service 99d393
                    raise MemoryError()
Packit Service 99d393
Packit Service 99d393
        cpdef extend(self, values):
Packit Service 99d393
            for value in values:
Packit Service 99d393
                self.append(value)
Packit Service 99d393
Packit Service 99d393
As a quick test with 10000 numbers on the author's machine indicates,
Packit Service 99d393
using this Queue from Cython code with C ``int`` values is about five
Packit Service 99d393
times as fast as using it from Cython code with Python object values,
Packit Service 99d393
almost eight times faster than using it from Python code in a Python
Packit Service 99d393
loop, and still more than twice as fast as using Python's highly
Packit Service 99d393
optimised ``collections.deque`` type from Cython code with Python
Packit Service 99d393
integers.
Packit Service 99d393
Packit Service 99d393
Packit Service 99d393
Callbacks
Packit Service 99d393
---------
Packit Service 99d393
Packit Service 99d393
Let's say you want to provide a way for users to pop values from the
Packit Service 99d393
queue up to a certain user defined event occurs.  To this end, you
Packit Service 99d393
want to allow them to pass a predicate function that determines when
Packit Service 99d393
to stop, e.g.::
Packit Service 99d393
Packit Service 99d393
    def pop_until(self, predicate):
Packit Service 99d393
        while not predicate(self.peek()):
Packit Service 99d393
            self.pop()
Packit Service 99d393
Packit Service 99d393
Now, let us assume for the sake of argument that the C queue
Packit Service 99d393
provides such a function that takes a C callback function as
Packit Service 99d393
predicate.  The API could look as follows::
Packit Service 99d393
Packit Service 99d393
    /* C type of a predicate function that takes a queue value and returns
Packit Service 99d393
     * -1 for errors
Packit Service 99d393
     *  0 for reject
Packit Service 99d393
     *  1 for accept
Packit Service 99d393
     */
Packit Service 99d393
    typedef int (*predicate_func)(void* user_context, QueueValue data);
Packit Service 99d393
Packit Service 99d393
    /* Pop values as long as the predicate evaluates to true for them,
Packit Service 99d393
     * returns -1 if the predicate failed with an error and 0 otherwise.
Packit Service 99d393
     */
Packit Service 99d393
    int queue_pop_head_until(Queue *queue, predicate_func predicate,
Packit Service 99d393
                             void* user_context);
Packit Service 99d393
Packit Service 99d393
It is normal for C callback functions to have a generic :c:type:`void*`
Packit Service 99d393
argument that allows passing any kind of context or state through the
Packit Service 99d393
C-API into the callback function.  We will use this to pass our Python
Packit Service 99d393
predicate function.
Packit Service 99d393
Packit Service 99d393
First, we have to define a callback function with the expected
Packit Service 99d393
signature that we can pass into the C-API function::
Packit Service 99d393
Packit Service 99d393
    cdef int evaluate_predicate(void* context, cqueue.QueueValue value):
Packit Service 99d393
        "Callback function that can be passed as predicate_func"
Packit Service 99d393
        try:
Packit Service 99d393
            # recover Python function object from void* argument
Packit Service 99d393
            func = <object>context
Packit Service 99d393
            # call function, convert result into 0/1 for True/False
Packit Service 99d393
            return bool(func(<int>value))
Packit Service 99d393
        except:
Packit Service 99d393
            # catch any Python errors and return error indicator
Packit Service 99d393
            return -1
Packit Service 99d393
Packit Service 99d393
The main idea is to pass a pointer (a.k.a. borrowed reference) to the
Packit Service 99d393
function object as the user context argument. We will call the C-API
Packit Service 99d393
function as follows::
Packit Service 99d393
Packit Service 99d393
    def pop_until(self, python_predicate_function):
Packit Service 99d393
        result = cqueue.queue_pop_head_until(
Packit Service 99d393
            self._c_queue, evaluate_predicate,
Packit Service 99d393
            <void*>python_predicate_function)
Packit Service 99d393
        if result == -1:
Packit Service 99d393
            raise RuntimeError("an error occurred")
Packit Service 99d393
Packit Service 99d393
The usual pattern is to first cast the Python object reference into
Packit Service 99d393
a :c:type:`void*` to pass it into the C-API function, and then cast
Packit Service 99d393
it back into a Python object in the C predicate callback function.
Packit Service 99d393
The cast to :c:type:`void*` creates a borrowed reference.  On the cast
Packit Service 99d393
to ``<object>``, Cython increments the reference count of the object
Packit Service 99d393
and thus converts the borrowed reference back into an owned reference.
Packit Service 99d393
At the end of the predicate function, the owned reference goes out
Packit Service 99d393
of scope again and Cython discards it.
Packit Service 99d393
Packit Service 99d393
The error handling in the code above is a bit simplistic. Specifically,
Packit Service 99d393
any exceptions that the predicate function raises will essentially be
Packit Service 99d393
discarded and only result in a plain ``RuntimeError()`` being raised
Packit Service 99d393
after the fact.  This can be improved by storing away the exception
Packit Service 99d393
in an object passed through the context parameter and re-raising it
Packit Service 99d393
after the C-API function has returned ``-1`` to indicate the error.