Commit 555cf2e1 authored by 0dminnimda's avatar 0dminnimda Committed by GitHub

Pythonise the documentation according to #4187: Basic Tutorial (cython_tutorial.rst) (GH-4226)

See https://github.com/cython/cython/issues/4187

* .gitignore: add directory for docs build and cython_debug
* doc-requirements.txt: add sphinx-tabs
* conf.py: add and setup sphinx-tabs extension
* Create _static\css\tabs.css for customisation
* add "two-syntax-variants-used" file as a preface about the different typing variants
parent b19b8d84
......@@ -24,6 +24,7 @@ Demos/*/*.html
/TEST_TMP/
/build/
/cython_build/
cython_debug/
/wheelhouse*/
!tests/build/
/dist/
......@@ -42,6 +43,7 @@ callgrind.out.*
.ipynb_checkpoints
docs/build
docs/_build
tags
TAGS
......
.sphinx-tabs {
margin-bottom: 1rem;
}
[role="tablist"] {
border-bottom: 0px solid #a0b3bf;
}
.sphinx-tabs-tab {
position: relative;
font-family: 'Helvetica Neue',Arial,Helvetica,sans-serif;
color: black;
line-height: 24px;
margin: 0;
font-size: 16px;
font-weight: 400;
background-color: rgba(255, 255, 255, 0);
border-radius: 5px 5px 5px 5px;
border: 0;
padding: 0.5rem 1.6rem;
margin-bottom: 0;
}
.sphinx-tabs-tab[aria-selected="true"] {
border: 2px solid gray;
border-bottom: 2px solid gray;
margin: -2px;
background-color: #efefef;
z-index: 999; /* render on top*/
}
.sphinx-tabs-tab[aria-selected="false"] {
border: 2px solid #dddddd;
border-bottom: 2px solid #dddddd;
margin: -2px;
background-color: white;
}
.sphinx-tabs-tab:focus {
z-index: 1;
outline-offset: 1px;
}
.sphinx-tabs-panel {
position: relative;
padding: 0rem;
border: 0px solid #a0b3bf;
margin: 0px 0px 0px 0px;
border-radius: 0 0 5px 5px;
border-top: 0;
background: white;
}
.sphinx-tabs-panel.code-tab {
padding: 0.4rem;
}
.sphinx-tab img {
margin-bottom: 24 px;
}
......@@ -39,6 +39,7 @@ extensions = [
'sphinx.ext.intersphinx',
'sphinx.ext.autodoc',
'sphinx_issues', # if this is missing, pip install sphinx-issues
'sphinx_tabs.tabs', # if this is missing, pip install sphinx-tabs
]
try: import rst2pdf
......@@ -133,6 +134,10 @@ imgmath_image_format = "svg"
issues_github_path = "cython/cython"
# For sphinx-tabs
sphinx_tabs_disable_tab_closing = True
# -- Options for HTML output ---------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
......@@ -174,6 +179,11 @@ html_favicon = "_static/favicon.ico"
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# Overwriting css from extensions
html_context = {
'css_files': ['_static/css/tabs.css'],
}
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y'
......
def primes(nb_primes: cython.int):
i: cython.int
p: cython.int[1000]
if nb_primes > 1000:
nb_primes = 1000
if not cython.compiled: # Only if regular Python is running
p = [0] * 1000 # Make p work almost like a C array
len_p: cython.int = 0 # The current number of elements in p.
n: cython.int = 2
while len_p < nb_primes:
# Is n prime?
for i in p[:len_p]:
if n % i == 0:
break
# If no break occurred in the loop, we have a prime.
else:
p[len_p] = n
len_p += 1
n += 1
# Let's copy the result into a Python list:
result_as_list = [prime for prime in p[:len_p]]
return result_as_list
def primes(int nb_primes):
cdef int n, i, len_p
cdef int p[1000]
if nb_primes > 1000:
nb_primes = 1000
len_p = 0 # The current number of elements in p.
n = 2
while len_p < nb_primes:
......@@ -18,6 +22,6 @@ def primes(int nb_primes):
len_p += 1
n += 1
# Let's return the result in a python list:
result_as_list = [prime for prime in p[:len_p]]
# Let's copy the result into a Python list:
result_as_list = [prime for prime in p[:len_p]]
return result_as_list
# distutils: language=c++
import cython
from cython.cimports.libcpp.vector import vector
def primes(nb_primes: cython.uint):
i: cython.int
p: vector[cython.int]
p.reserve(nb_primes) # allocate memory for 'nb_primes' elements.
n: cint = 2
while p.size() < nb_primes: # size() for vectors is similar to len()
for i in p:
if n % i == 0:
break
else:
p.push_back(n) # push_back is similar to append()
n += 1
# If possible, C values and C++ objects are automatically
# converted to Python objects at need.
return p # so here, the vector will be copied into a Python list.
# distutils: language=c++
from libcpp.vector cimport vector
def primes(unsigned int nb_primes):
......@@ -16,6 +17,6 @@ def primes(unsigned int nb_primes):
p.push_back(n) # push_back is similar to append()
n += 1
# Vectors are automatically converted to Python
# lists when converted to Python objects.
return p
# If possible, C values and C++ objects are automatically
# converted to Python objects at need.
return p # so here, the vector will be copied into a Python list.
def primes_python(nb_primes):
def primes(nb_primes):
p = []
n = 2
while len(p) < nb_primes:
......
from setuptools import setup
from Cython.Build import cythonize
setup(
ext_modules=cythonize("fib.pyx"),
)
.. warning::
This code uses an external native (non-Python) library through a ``cimport``.
Cython compilation enables this, but there is no support for this from
plain Python. Trying to run this code from Python (without compilation)
will fail when accessing the external library.
This is described in more detail in :ref:`calling-c-functions`.
......@@ -6,6 +6,9 @@
Basic Tutorial
**************
.. include::
../two-syntax-variants-used
The Basics of Cython
====================
......@@ -103,9 +106,14 @@ Now following the steps for the Hello World example we first rename the file
to have a `.pyx` extension, lets say :file:`fib.pyx`, then we create the
:file:`setup.py` file. Using the file created for the Hello World example, all
that you need to change is the name of the Cython filename, and the resulting
module name, doing this we have:
module name, doing this we have::
from setuptools import setup
from Cython.Build import cythonize
.. literalinclude:: ../../examples/tutorial/cython_tutorial/setup.py
setup(
ext_modules=cythonize("fib.pyx"),
)
Build the extension with the same command used for the helloworld.pyx:
......@@ -127,29 +135,59 @@ Primes
Here's a small example showing some of what can be done. It's a routine for
finding prime numbers. You tell it how many primes you want, and it returns
them as a Python list.
.. tabs::
.. group-tab:: Pure Python
:file:`primes.pyx`:
.. literalinclude:: ../../examples/tutorial/cython_tutorial/primes.py
:linenos:
:caption: primes.py
.. literalinclude:: ../../examples/tutorial/cython_tutorial/primes.pyx
:linenos:
.. group-tab:: Cython
.. literalinclude:: ../../examples/tutorial/cython_tutorial/primes.pyx
:linenos:
:caption: primes.pyx
You'll see that it starts out just like a normal Python function definition,
except that the parameter ``nb_primes`` is declared to be of type ``int`` . This
except that the parameter ``nb_primes`` is declared to be of type ``int``. This
means that the object passed will be converted to a C integer (or a
``TypeError.`` will be raised if it can't be).
Now, let's dig into the core of the function::
Now, let's dig into the core of the function:
.. tabs::
.. group-tab:: Pure Python
.. literalinclude:: ../../examples/tutorial/cython_tutorial/primes.py
:lines: 2,3
:dedent:
:lineno-start: 2
.. literalinclude:: ../../examples/tutorial/cython_tutorial/primes.py
:lines: 11,12
:dedent:
:lineno-start: 11
Lines 2, 3, 11 and 12 use the variable annotations
to define some local C variables.
The result is stored in the C array ``p`` during processing,
and will be copied into a Python list at the end (line 26).
.. group-tab:: Cython
cdef int n, i, len_p
cdef int p[1000]
.. literalinclude:: ../../examples/tutorial/cython_tutorial/primes.pyx
:lines: 2,3
:dedent:
:lineno-start: 2
Lines 2 and 3 use the ``cdef`` statement to define some local C variables.
The result is stored in the C array ``p`` during processing,
and will be copied into a Python list at the end (line 22).
Lines 2 and 3 use the ``cdef`` statement to define some local C variables.
The result is stored in the C array ``p`` during processing,
and will be copied into a Python list at the end (line 26).
.. NOTE:: You cannot create very large arrays in this manner, because
they are allocated on the C function call :term:`stack<Stack allocation>`, which is a
rather precious and scarce resource.
they are allocated on the C function call :term:`stack<Stack allocation>`,
which is a rather precious and scarce resource.
To request larger arrays,
or even arrays with a length only known at runtime,
you can learn how to make efficient use of
......@@ -157,61 +195,83 @@ and will be copied into a Python list at the end (line 22).
:ref:`Python arrays <array-array>`
or :ref:`NumPy arrays <memoryviews>` with Cython.
::
if nb_primes > 1000:
nb_primes = 1000
.. literalinclude:: ../../examples/tutorial/cython_tutorial/primes.pyx
:lines: 5,6
:dedent:
:lineno-start: 5
As in C, declaring a static array requires knowing the size at compile time.
We make sure the user doesn't set a value above 1000 (or we would have a
segmentation fault, just like in C). ::
segmentation fault, just like in C)
.. tabs::
.. group-tab:: Pure Python
.. literalinclude:: ../../examples/tutorial/cython_tutorial/primes.py
:lines: 8,9
:dedent:
:lineno-start: 8
When we run this code from Python, we have to initialize the items in the array.
This is most easily done by filling it with zeros (as seen on line 8-9).
When we compile this with Cython, on the other hand, the array will
behave as in C. It is allocated on the function call stack with a fixed
length of 1000 items that contain arbitrary data from the last time that
memory was used. We will then overwrite those items in our calculation.
.. literalinclude:: ../../examples/tutorial/cython_tutorial/primes.py
:lines: 10-13
:dedent:
:lineno-start: 10
len_p = 0 # The number of elements in p
n = 2
while len_p < nb_primes:
.. group-tab:: Cython
Lines 7-9 set up for a loop which will test candidate numbers for primeness
until the required number of primes has been found. ::
.. literalinclude:: ../../examples/tutorial/cython_tutorial/primes.pyx
:lines: 10-13
:dedent:
:lineno-start: 10
# Is n prime?
for i in p[:len_p]:
if n % i == 0:
break
Lines 11-13 set up a while loop which will test numbers-candidates to primes
until the required number of primes has been found.
Lines 11-12, which try dividing a candidate by all the primes found so far,
.. literalinclude:: ../../examples/tutorial/cython_tutorial/primes.pyx
:lines: 14-17
:dedent:
:lineno-start: 14
Lines 15-16, which try to divide a candidate by all the primes found so far,
are of particular interest. Because no Python objects are referred to,
the loop is translated entirely into C code, and thus runs very fast.
You will notice the way we iterate over the ``p`` C array. ::
You will notice the way we iterate over the ``p`` C array.
for i in p[:len_p]:
.. literalinclude:: ../../examples/tutorial/cython_tutorial/primes.pyx
:lines: 15
:dedent:
:lineno-start: 15
The loop gets translated into a fast C loop and works just like iterating
over a Python list or NumPy array. If you don't slice the C array with
``[:len_p]``, then Cython will loop over the 1000 elements of the array.
::
# If no break occurred in the loop
else:
p[len_p] = n
len_p += 1
n += 1
.. literalinclude:: ../../examples/tutorial/cython_tutorial/primes.pyx
:lines: 19-23
:dedent:
:lineno-start: 19
If no breaks occurred, it means that we found a prime, and the block of code
after the ``else`` line 16 will be executed. We add the prime found to ``p``.
after the ``else`` line 20 will be executed. We add the prime found to ``p``.
If you find having an ``else`` after a for-loop strange, just know that it's a
lesser known features of the Python language, and that Cython executes it at
C speed for you.
If the for-else syntax confuses you, see this excellent
`blog post <https://shahriar.svbtle.com/pythons-else-clause-in-loops>`_.
::
# Let's put the result in a python list:
result_as_list = [prime for prime in p[:len_p]]
return result_as_list
.. literalinclude:: ../../examples/tutorial/cython_tutorial/primes.pyx
:lines: 25-27
:dedent:
:lineno-start: 25
In line 22, before returning the result, we need to copy our C array into a
In line 26, before returning the result, we need to copy our C array into a
Python list, because Python can't read C arrays. Cython can automatically
convert many C types from and to Python types, as described in the
documentation on :ref:`type conversion <type-conversion>`, so we can use
......@@ -225,11 +285,20 @@ Because the variable ``result_as_list`` hasn't been explicitly declared with a t
it is assumed to hold a Python object, and from the assignment, Cython also knows
that the exact type is a Python list.
Finally, at line 18, a normal
Python return statement returns the result list.
Finally, at line 27, a normal Python return statement returns the result list.
.. tabs::
.. group-tab:: Pure Python
Compiling primes.py with the Cython compiler produces an extension module
which we can try out in the interactive interpreter as follows:
Compiling primes.pyx with the Cython compiler produces an extension module
which we can try out in the interactive interpreter as follows::
.. group-tab:: Cython
Compiling primes.pyx with the Cython compiler produces an extension module
which we can try out in the interactive interpreter as follows:
.. code-block:: python
>>> import primes
>>> primes.primes(10)
......@@ -238,12 +307,20 @@ which we can try out in the interactive interpreter as follows::
See, it works! And if you're curious about how much work Cython has saved you,
take a look at the C code generated for this module.
Cython has a way to visualise where interaction with Python objects and
Python's C-API is taking place. For this, pass the
``annotate=True`` parameter to ``cythonize()``. It produces a HTML file. Let's see:
.. figure:: htmlreport.png
.. tabs::
.. group-tab:: Pure Python
.. figure:: htmlreport_py.png
:scale: 90 %
.. group-tab:: Cython
.. figure:: htmlreport_pyx.png
:scale: 90 %
If a line is white, it means that the code generated doesn't interact
with Python, so will run as fast as normal C code. The darker the yellow, the more
......@@ -262,42 +339,64 @@ Python behavior, the language will perform division checks at runtime,
just like Python does. You can deactivate those checks by using the
:ref:`compiler directives<compiler-directives>`.
Now let's see if, even if we have division checks, we obtained a boost in speed.
Let's write the same program, but Python-style:
Now let's see if we get a speed increase even if there is a division check.
Let's write the same program, but in Python:
.. literalinclude:: ../../examples/tutorial/cython_tutorial/primes_python.py
:caption: primes_python.py / primes_python_compiled.py
It is also possible to take a plain ``.py`` file and to compile it with Cython.
Let's take ``primes_python``, change the function name to ``primes_python_compiled`` and
compile it with Cython (without changing the code). We will also change the name of the
file to ``example_py_cy.py`` to differentiate it from the others.
Now the ``setup.py`` looks like this::
It is possible to take a plain (unannotated) ``.py`` file and to compile it with Cython.
Let's create a copy of ``primes_python`` and name it ``primes_python_compiled``
to be able to compare it to the (non-compiled) Python module.
Then we compile that file with Cython, without changing the code.
Now the ``setup.py`` looks like this:
from setuptools import setup
from Cython.Build import cythonize
.. tabs::
.. group-tab:: Pure Python
setup(
ext_modules=cythonize(['example.pyx', # Cython code file with primes() function
'example_py_cy.py'], # Python code file with primes_python_compiled() function
annotate=True), # enables generation of the html annotation file
)
.. code-block:: python
from setuptools import setup
from Cython.Build import cythonize
setup(
ext_modules=cythonize(
['primes.py', # Cython code file with primes() function
'primes_python_compiled.py'], # Python code file with primes() function
annotate=True), # enables generation of the html annotation file
)
.. group-tab:: Cython
.. code-block:: python
from setuptools import setup
from Cython.Build import cythonize
setup(
ext_modules=cythonize(
['primes.pyx', # Cython code file with primes() function
'primes_python_compiled.py'], # Python code file with primes() function
annotate=True), # enables generation of the html annotation file
)
Now we can ensure that those two programs output the same values::
>>> primes_python(1000) == primes(1000)
>>> import primes, primes_python, primes_python_compiled
>>> primes_python.primes(1000) == primes.primes(1000)
True
>>> primes_python_compiled(1000) == primes(1000)
>>> primes_python_compiled.primes(1000) == primes.primes(1000)
True
It's possible to compare the speed now::
python -m timeit -s 'from example_py import primes_python' 'primes_python(1000)'
python -m timeit -s 'from primes_python import primes' 'primes(1000)'
10 loops, best of 3: 23 msec per loop
python -m timeit -s 'from example_py_cy import primes_python_compiled' 'primes_python_compiled(1000)'
python -m timeit -s 'from primes_python_compiled import primes' 'primes(1000)'
100 loops, best of 3: 11.9 msec per loop
python -m timeit -s 'from example import primes' 'primes(1000)'
python -m timeit -s 'from primes import primes' 'primes(1000)'
1000 loops, best of 3: 1.65 msec per loop
The cythonize version of ``primes_python`` is 2 times faster than the Python one,
......@@ -325,9 +424,9 @@ Primes with C++
With Cython, it is also possible to take advantage of the C++ language, notably,
part of the C++ standard library is directly importable from Cython code.
Let's see what our :file:`primes.pyx` becomes when
using `vector <https://en.cppreference.com/w/cpp/container/vector>`_ from the C++
standard library.
Let's see what our code becomes when using
`vector <https://en.cppreference.com/w/cpp/container/vector>`_
from the C++ standard library.
.. note::
......@@ -338,8 +437,19 @@ standard library.
how many elements you are going to put in the vector. For more details
see `this page from cppreference <https://en.cppreference.com/w/cpp/container/vector>`_.
.. literalinclude:: ../../examples/tutorial/cython_tutorial/primes_cpp.pyx
:linenos:
.. tabs::
.. group-tab:: Pure Python
.. literalinclude:: ../../examples/tutorial/cython_tutorial/primes_cpp.py
:linenos:
.. include::
../cimport-warning
.. group-tab:: Cython
.. literalinclude:: ../../examples/tutorial/cython_tutorial/primes_cpp.pyx
:linenos:
The first line is a compiler directive. It tells Cython to compile your code to C++.
This will enable the use of C++ language features and the C++ standard library.
......@@ -357,4 +467,3 @@ Language Details
For more about the Cython language, see :ref:`language-basics`.
To dive right in to using Cython in a numerical computation context,
see :ref:`memoryviews`.
......@@ -340,6 +340,8 @@ improve the type analysis in Cython.
Tips and Tricks
---------------
.. _calling-c-functions:
Calling C functions
^^^^^^^^^^^^^^^^^^^
......
docs/src/tutorial/python_division.png

7.97 KB | W: | H:

docs/src/tutorial/python_division.png

73.3 KB | W: | H:

docs/src/tutorial/python_division.png
docs/src/tutorial/python_division.png
docs/src/tutorial/python_division.png
docs/src/tutorial/python_division.png
  • 2-up
  • Swipe
  • Onion skin
This page uses two different syntax variants: the Cython specific ``cdef`` syntax
and static Cython type declarations in
:ref:`pure Python code <pep484_type_annotations>`,
following `PEP-484 <https://www.python.org/dev/peps/pep-0484/>`_ type hints
and `PEP 526 <https://www.python.org/dev/peps/pep-0526/>`_ variable annotations.
To make good use of the latter, including C data types, etc., you need
the special ``cython`` module, which you can import with
.. code-block:: python
import cython
in the Python module that you want to compile.
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