Commit d055de85 authored by gabrieldemarmiesse's avatar gabrieldemarmiesse

Used intptr_t instead of int to avoid warnings (and crashes in C++). Removed old examples files.

parent 3d291a58
# queue.pyx
from libc.stdint cimport intptr_t
cimport cqueue
cdef class Queue:
"""A queue class for C integer values.
>>> q = Queue()
>>> q.append(5)
>>> q.peek()
5
>>> q.pop()
5
"""
cdef cqueue.Queue* _c_queue
def __cinit__(self):
self._c_queue = cqueue.queue_new()
if self._c_queue is NULL:
raise MemoryError()
def __dealloc__(self):
if self._c_queue is not NULL:
cqueue.queue_free(self._c_queue)
cpdef append(self, intptr_t value):
if not cqueue.queue_push_tail(self._c_queue,
<void*> value):
raise MemoryError()
cdef extend(self, intptr_t* values, size_t count):
cdef size_t i
for i in range(count):
if not cqueue.queue_push_tail(
self._c_queue, <void*> values[i]):
raise MemoryError()
# The `cpdef` feature is obviously not available for the `extend()`
# method, as the method signature is incompatible with Python argument
# types (Python doesn't have pointers). However, we can make a method
# called `extend_python` instead that accepts an arbitrary Python iterable.
cpdef extend_python(self, values):
for value in values:
self.append(value)
cpdef intptr_t peek(self) except? -1:
cdef intptr_t value = <intptr_t> cqueue.queue_peek_head(self._c_queue)
if value == 0:
# this may mean that the queue is empty,
# or that it happens to contain a 0 value
if cqueue.queue_is_empty(self._c_queue):
raise IndexError("Queue is empty")
return value
cpdef intptr_t pop(self) except? -1:
if cqueue.queue_is_empty(self._c_queue):
raise IndexError("Queue is empty")
return <intptr_t> cqueue.queue_pop_head(self._c_queue)
def __bool__(self):
return not cqueue.queue_is_empty(self._c_queue)
...@@ -318,28 +318,27 @@ to give them a straight C interface. ...@@ -318,28 +318,27 @@ to give them a straight C interface.
In C, it is common for data structures to store data as a ``void*`` to In C, it is common for data structures to store data as a ``void*`` to
whatever data item type. Since we only want to store ``int`` values, whatever data item type. Since we only want to store ``int`` values,
which usually fit into the size of a pointer type, we can avoid which usually fit into the size of a pointer type, we will use ``intptr_t``
additional memory allocations through a trick: we cast our ``int`` values as it is garanteed to be at least as big as an ``int`` and the same size
to ``void*`` and vice versa, and store the value directly as the as a ``void*``
pointer value.
Here is a simple implementation for the ``append()`` method:: Here is a simple implementation for the ``append()`` method::
cdef append(self, int value): cdef append(self, intptr_t value):
cqueue.queue_push_tail(self._c_queue, <void*>value) cqueue.queue_push_tail(self._c_queue, <void*>value)
Again, the same error handling considerations as for the Again, the same error handling considerations as for the
``__cinit__()`` method apply, so that we end up with this ``__cinit__()`` method apply, so that we end up with this
implementation instead:: implementation instead::
cdef append(self, int value): cdef append(self, intptr_t value):
if not cqueue.queue_push_tail(self._c_queue, if not cqueue.queue_push_tail(self._c_queue,
<void*>value): <void*>value):
raise MemoryError() raise MemoryError()
Adding an ``extend()`` method should now be straight forward:: Adding an ``extend()`` method should now be straight forward::
cdef extend(self, int* values, size_t count): cdef extend(self, intptr_t* values, size_t count):
"""Append all ints to the queue. """Append all ints to the queue.
""" """
cdef size_t i cdef size_t i
...@@ -355,11 +354,11 @@ So far, we can only add data to the queue. The next step is to write ...@@ -355,11 +354,11 @@ So far, we can only add data to the queue. The next step is to write
the two methods to get the first element: ``peek()`` and ``pop()``, the two methods to get the first element: ``peek()`` and ``pop()``,
which provide read-only and destructive read access respectively:: which provide read-only and destructive read access respectively::
cdef int peek(self): cdef intptr_t peek(self):
return <int>cqueue.queue_peek_head(self._c_queue) return <intptr_t>cqueue.queue_peek_head(self._c_queue)
cdef int pop(self): cdef intptr_t pop(self):
return <int>cqueue.queue_pop_head(self._c_queue) return <intptr_t>cqueue.queue_pop_head(self._c_queue)
Handling errors Handling errors
...@@ -375,8 +374,8 @@ first case to raise an exception, whereas the second case should ...@@ -375,8 +374,8 @@ first case to raise an exception, whereas the second case should
simply return ``0``. To deal with this, we need to special case this simply return ``0``. To deal with this, we need to special case this
value, and check if the queue really is empty or not:: value, and check if the queue really is empty or not::
cdef int peek(self) except? -1: cdef intptr_t peek(self) except? -1:
value = <int>cqueue.queue_peek_head(self._c_queue) value = <intptr_t>cqueue.queue_peek_head(self._c_queue)
if value == 0: if value == 0:
# this may mean that the queue is empty, or # this may mean that the queue is empty, or
# that it happens to contain a 0 value # that it happens to contain a 0 value
...@@ -425,10 +424,10 @@ also needs adaptation. Since it removes a value from the queue, ...@@ -425,10 +424,10 @@ also needs adaptation. Since it removes a value from the queue,
however, it is not enough to test if the queue is empty *after* the however, it is not enough to test if the queue is empty *after* the
removal. Instead, we must test it on entry:: removal. Instead, we must test it on entry::
cdef int pop(self) except? -1: cdef intptr_t pop(self) except? -1:
if cqueue.queue_is_empty(self._c_queue): if cqueue.queue_is_empty(self._c_queue):
raise IndexError("Queue is empty") raise IndexError("Queue is empty")
return <int>cqueue.queue_pop_head(self._c_queue) return <intptr_t>cqueue.queue_pop_head(self._c_queue)
The return value for exception propagation is declared exactly as for The return value for exception propagation is declared exactly as for
``peek()``. ``peek()``.
...@@ -467,77 +466,9 @@ methods even when they are called from Cython. This adds a tiny overhead ...@@ -467,77 +466,9 @@ methods even when they are called from Cython. This adds a tiny overhead
compared to ``cdef`` methods. compared to ``cdef`` methods.
The following listing shows the complete implementation that uses The following listing shows the complete implementation that uses
``cpdef`` methods where possible:: ``cpdef`` methods where possible:
cimport cqueue
cdef class Queue:
"""A queue class for C integer values.
>>> q = Queue()
>>> q.append(5)
>>> q.peek()
5
>>> q.pop()
5
"""
cdef cqueue.Queue* _c_queue
def __cinit__(self):
self._c_queue = cqueue.queue_new()
if self._c_queue is NULL:
raise MemoryError()
def __dealloc__(self):
if self._c_queue is not NULL:
cqueue.queue_free(self._c_queue)
cpdef append(self, int value):
if not cqueue.queue_push_tail(self._c_queue,
<void*>value):
raise MemoryError()
cdef extend(self, int* values, size_t count):
cdef size_t i
for i in xrange(count):
if not cqueue.queue_push_tail(
self._c_queue, <void*>values[i]):
raise MemoryError()
cpdef int peek(self) except? -1:
cdef int value = \
<int>cqueue.queue_peek_head(self._c_queue)
if value == 0:
# this may mean that the queue is empty,
# or that it happens to contain a 0 value
if cqueue.queue_is_empty(self._c_queue):
raise IndexError("Queue is empty")
return value
cpdef int pop(self) except? -1:
if cqueue.queue_is_empty(self._c_queue):
raise IndexError("Queue is empty")
return <int>cqueue.queue_pop_head(self._c_queue)
def __bool__(self):
return not cqueue.queue_is_empty(self._c_queue)
The ``cpdef`` feature is obviously not available for the ``extend()``
method, as the method signature is incompatible with Python argument
types. However, if wanted, we can rename the C-ish ``extend()``
method to e.g. ``c_extend()``, and write a new ``extend()`` method
instead that accepts an arbitrary Python iterable::
cdef c_extend(self, int* values, size_t count):
cdef size_t i
for i in range(count):
if not cqueue.queue_push_tail(
self._c_queue, <void*>values[i]):
raise MemoryError()
cpdef extend(self, values):
for value in values:
self.append(value)
.. literalinclude:: ../../examples/tutorial/clibraries/queue3.pyx
Now we can test our Queue implementation using a python script, Now we can test our Queue implementation using a python script,
for example here :file:`test_queue.py`: for example here :file:`test_queue.py`:
...@@ -545,7 +476,7 @@ for example here :file:`test_queue.py`: ...@@ -545,7 +476,7 @@ for example here :file:`test_queue.py`:
.. literalinclude:: ../../examples/tutorial/clibraries/test_queue.py .. literalinclude:: ../../examples/tutorial/clibraries/test_queue.py
As a quick test with 10000 numbers on the author's machine indicates, As a quick test with 10000 numbers on the author's machine indicates,
using this Queue from Cython code with C ``int`` values is about five using this Queue from Cython code with C ``intptr_t`` values is about five
times as fast as using it from Cython code with Python object values, times as fast as using it from Cython code with Python object values,
almost eight times faster than using it from Python code in a Python almost eight times faster than using it from Python code in a Python
loop, and still more than twice as fast as using Python's highly loop, and still more than twice as fast as using Python's highly
...@@ -574,12 +505,12 @@ predicate. The API could look as follows:: ...@@ -574,12 +505,12 @@ predicate. The API could look as follows::
* 0 for reject * 0 for reject
* 1 for accept * 1 for accept
*/ */
typedef int (*predicate_func)(void* user_context, QueueValue data); typedef intptr_t (*predicate_func)(void* user_context, QueueValue data);
/* Pop values as long as the predicate evaluates to true for them, /* Pop values as long as the predicate evaluates to true for them,
* returns -1 if the predicate failed with an error and 0 otherwise. * returns -1 if the predicate failed with an error and 0 otherwise.
*/ */
int queue_pop_head_until(Queue *queue, predicate_func predicate, intptr_t queue_pop_head_until(Queue *queue, predicate_func predicate,
void* user_context); void* user_context);
It is normal for C callback functions to have a generic :c:type:`void*` It is normal for C callback functions to have a generic :c:type:`void*`
...@@ -590,13 +521,13 @@ predicate function. ...@@ -590,13 +521,13 @@ predicate function.
First, we have to define a callback function with the expected First, we have to define a callback function with the expected
signature that we can pass into the C-API function:: signature that we can pass into the C-API function::
cdef int evaluate_predicate(void* context, cqueue.QueueValue value): cdef intptr_t evaluate_predicate(void* context, cqueue.QueueValue value):
"Callback function that can be passed as predicate_func" "Callback function that can be passed as predicate_func"
try: try:
# recover Python function object from void* argument # recover Python function object from void* argument
func = <object>context func = <object>context
# call function, convert result into 0/1 for True/False # call function, convert result into 0/1 for True/False
return bool(func(<int>value)) return bool(func(<intptr_t>value))
except: except:
# catch any Python errors and return error indicator # catch any Python errors and return error indicator
return -1 return -1
......
cdef extern from "libcalg/queue.h":
ctypedef struct Queue:
pass
ctypedef void* QueueValue
Queue* queue_new()
void queue_free(Queue* queue)
int queue_push_head(Queue* queue, QueueValue data)
QueueValue queue_pop_head(Queue* queue)
QueueValue queue_peek_head(Queue* queue)
int queue_push_tail(Queue* queue, QueueValue data)
QueueValue queue_pop_tail(Queue* queue)
QueueValue queue_peek_tail(Queue* queue)
int queue_is_empty(Queue* queue)
cimport cqueue
cdef class Queue:
cdef cqueue.Queue* _c_queue
def __cinit__(self):
self._c_queue = cqueue.queue_new()
if self._c_queue is NULL:
raise MemoryError()
def __dealloc__(self):
if self._c_queue is not NULL:
cqueue.queue_free(self._c_queue)
cpdef int append(self, int value) except -1:
if not cqueue.queue_push_tail(self._c_queue, <void*>value):
raise MemoryError()
return 0
cdef int extend(self, int* values, Py_ssize_t count) except -1:
cdef Py_ssize_t i
for i in range(count):
if not cqueue.queue_push_tail(self._c_queue, <void*>values[i]):
raise MemoryError()
return 0
cpdef int peek(self) except? 0:
cdef int value = <int>cqueue.queue_peek_head(self._c_queue)
if value == 0:
# this may mean that the queue is empty, or that it
# happens to contain a 0 value
if cqueue.queue_is_empty(self._c_queue):
raise IndexError("Queue is empty")
return value
cpdef int pop(self) except? 0:
cdef int value = <int>cqueue.queue_pop_head(self._c_queue)
if value == 0:
# this may mean that the queue is empty, or that it
# happens to contain a 0 value
if cqueue.queue_is_empty(self._c_queue):
raise IndexError("Queue is empty")
return value
def __bool__(self): # same as __nonzero__ in Python 2.x
return not cqueue.queue_is_empty(self._c_queue)
DEF repeat_count=10000
def test_cy():
cdef int i
cdef Queue q = Queue()
for i in range(repeat_count):
q.append(i)
for i in range(repeat_count):
q.peek()
while q:
q.pop()
def test_py():
cdef int i
q = Queue()
for i in range(repeat_count):
q.append(i)
for i in range(repeat_count):
q.peek()
while q:
q.pop()
from collections import deque
def test_deque():
cdef int i
q = deque()
for i in range(repeat_count):
q.appendleft(i)
for i in range(repeat_count):
q[-1]
while q:
q.pop()
repeat = range(repeat_count)
def test_py_exec():
q = Queue()
d = dict(q=q, repeat=repeat)
exec u"""\
for i in repeat:
q.append(9)
for i in repeat:
q.peek()
while q:
q.pop()
""" in d
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment