Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
C
cython
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Kirill Smelkov
cython
Commits
b24cbfba
Commit
b24cbfba
authored
Jun 20, 2018
by
Stefan Behnel
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
docs: Give a better explanation for changing the extend() method in the C libraries tutorial.
See #2362.
parent
88034bdc
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
34 additions
and
13 deletions
+34
-13
docs/examples/tutorial/clibraries/queue3.pyx
docs/examples/tutorial/clibraries/queue3.pyx
+5
-4
docs/src/tutorial/clibraries.rst
docs/src/tutorial/clibraries.rst
+29
-9
No files found.
docs/examples/tutorial/clibraries/queue3.pyx
View file @
b24cbfba
...
@@ -27,11 +27,12 @@ cdef class Queue:
...
@@ -27,11 +27,12 @@ cdef class Queue:
<
void
*>
value
):
<
void
*>
value
):
raise
MemoryError
()
raise
MemoryError
()
# The `cpdef` feature is obviously not available for the
`extend()`
# The `cpdef` feature is obviously not available for the
original "extend()"
# method, as the method signature is incompatible with Python argument
# method, as the method signature is incompatible with Python argument
# types (Python doesn't have pointers). However, we can rename
# types (Python does not have pointers). However, we can rename
# the C-ish `extend()` method to e.g. `extend_ints()`, and write
# the C-ish "extend()" method to e.g. "extend_ints()", and write
# a new `extend()` method instead that accepts an arbitrary Python iterable.
# a new "extend()" method that provides a suitable Python interface by
# accepting an arbitrary Python iterable.
cpdef
extend
(
self
,
values
):
cpdef
extend
(
self
,
values
):
for
value
in
values
:
for
value
in
values
:
self
.
append
(
value
)
self
.
append
(
value
)
...
...
docs/src/tutorial/clibraries.rst
View file @
b24cbfba
...
@@ -348,14 +348,14 @@ Adding an ``extend()`` method should now be straight forward::
...
@@ -348,14 +348,14 @@ Adding an ``extend()`` method should now be straight forward::
self._c_queue, <void*>values[i]):
self._c_queue, <void*>values[i]):
raise MemoryError()
raise MemoryError()
This becomes handy when reading values from a NumPy array, for
This becomes handy when reading values from a C array, for example.
example.
So far, we can only add data to the queue. The next step is to write
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.
To avoid the compiler warning when casting ``void*`` to ``int`` directly,
To avoid compiler warnings when casting ``void*`` to ``int`` directly,
we use an intermediate data type big enough to hold a ``void*``. Here ``Py_ssize_t``::
we use an intermediate data type that is big enough to hold a ``void*``.
Here, ``Py_ssize_t``::
cdef int peek(self):
cdef int peek(self):
return <Py_ssize_t>cqueue.queue_peek_head(self._c_queue)
return <Py_ssize_t>cqueue.queue_peek_head(self._c_queue)
...
@@ -363,19 +363,26 @@ we use an intermediate data type big enough to hold a ``void*``. Here ``Py_ssize
...
@@ -363,19 +363,26 @@ we use an intermediate data type big enough to hold a ``void*``. Here ``Py_ssize
cdef int pop(self):
cdef int pop(self):
return <Py_ssize_t>cqueue.queue_pop_head(self._c_queue)
return <Py_ssize_t>cqueue.queue_pop_head(self._c_queue)
Normally, in C, we risk loosing data when we convert a larger integer type
to a smaller integer type without checking the boundaries, and ``Py_ssize_t``
may be a larger type than ``int``. But since we control how values are added
to the queue, we already know that all values that are in the queue fit into
an ``int``, so the above conversion from ``void*`` to ``Py_ssize_t`` to ``int``
(the return type) is safe by design.
Handling errors
Handling errors
---------------
---------------
Now, what happens when the queue is empty? According to the
Now, what happens when the queue is empty? According to the
documentation, the functions return a ``NULL`` pointer, which is
documentation, the functions return a ``NULL`` pointer, which is
typically not a valid value.
S
ince we are simply casting to and
typically not a valid value.
But s
ince we are simply casting to and
from ints, we cannot distinguish anymore if the return value was
from ints, we cannot distinguish anymore if the return value was
``NULL`` because the queue was empty or because the value stored in
``NULL`` because the queue was empty or because the value stored in
the queue was ``0``.
However, in Cython code, we would expect the
the queue was ``0``.
In Cython code, we want the first case to
first case to raise an exception, whereas the second case should
raise an exception, whereas the second case should simply return
simply return ``0``. To deal with this, we need to special case this
``0``. To deal with this, we need to special case this value,
value,
and check if the queue really is empty or not::
and check if the queue really is empty or not::
cdef int peek(self) except? -1:
cdef int peek(self) except? -1:
cdef int value = <Py_ssize_t>cqueue.queue_peek_head(self._c_queue)
cdef int value = <Py_ssize_t>cqueue.queue_peek_head(self._c_queue)
...
@@ -468,6 +475,19 @@ methods ensure that they can be appropriately overridden by Python
...
@@ -468,6 +475,19 @@ methods ensure that they can be appropriately overridden by Python
methods even when they are called from Cython. This adds a tiny overhead
methods even when they are called from Cython. This adds a tiny overhead
compared to ``cdef`` methods.
compared to ``cdef`` methods.
Now that we have both a C-interface and a Python interface for our
class, we should make sure that both interfaces are consistent.
Python users would expect an ``extend()`` method that accepts arbitrary
iterables, whereas C users would like to have one that allows passing
C arrays and C memory. Both signatures are incompatible.
We will solve this issue by considering that in C, the API could also
want to support other input types, e.g. arrays of ``long`` or ``char``,
which is usually supported with differently named C API functions such as
``extend_ints()``, ``extend_longs()``, extend_chars()``, etc. This allows
us to free the method name ``extend()`` for the duck typed Python method,
which can accept arbitrary iterables.
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:
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment