|
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.
|