From d7efda4378732b703e6f9125ce5eec1868f71971 Mon Sep 17 00:00:00 2001 From: Matus Valo Date: Thu, 15 Jul 2021 09:06:05 +0200 Subject: docs: Add pure Python examples to "Getting started" guide (GH-4283) --- docs/examples/quickstart/cythonize/cdef_keyword.py | 4 ++ .../examples/quickstart/cythonize/cdef_keyword.pyx | 2 + .../quickstart/cythonize/htmlreport_py.png | Bin 0 -> 21568 bytes .../quickstart/cythonize/htmlreport_pyx.png | Bin 0 -> 19384 bytes docs/examples/quickstart/cythonize/integrate_cy.py | 13 +++++ .../examples/quickstart/cythonize/integrate_cy.pyx | 3 +- docs/src/quickstart/build.rst | 32 ++++++++--- docs/src/quickstart/cythonize.rst | 59 ++++++++++++++++----- docs/src/quickstart/htmlreport.png | Bin 19181 -> 0 bytes 9 files changed, 93 insertions(+), 20 deletions(-) create mode 100644 docs/examples/quickstart/cythonize/cdef_keyword.py create mode 100755 docs/examples/quickstart/cythonize/htmlreport_py.png create mode 100755 docs/examples/quickstart/cythonize/htmlreport_pyx.png create mode 100644 docs/examples/quickstart/cythonize/integrate_cy.py delete mode 100644 docs/src/quickstart/htmlreport.png diff --git a/docs/examples/quickstart/cythonize/cdef_keyword.py b/docs/examples/quickstart/cythonize/cdef_keyword.py new file mode 100644 index 000000000..6c0ee3e68 --- /dev/null +++ b/docs/examples/quickstart/cythonize/cdef_keyword.py @@ -0,0 +1,4 @@ +@cython.cfunc +@cython.exceptval(-2, check=True) +def f(x: cython.double) -> cython.double: + return x ** 2 - x diff --git a/docs/examples/quickstart/cythonize/cdef_keyword.pyx b/docs/examples/quickstart/cythonize/cdef_keyword.pyx index ba1e46272..bc7d893fa 100644 --- a/docs/examples/quickstart/cythonize/cdef_keyword.pyx +++ b/docs/examples/quickstart/cythonize/cdef_keyword.pyx @@ -1,2 +1,4 @@ + + cdef double f(double x) except? -2: return x ** 2 - x diff --git a/docs/examples/quickstart/cythonize/htmlreport_py.png b/docs/examples/quickstart/cythonize/htmlreport_py.png new file mode 100755 index 000000000..0303f4b6e Binary files /dev/null and b/docs/examples/quickstart/cythonize/htmlreport_py.png differ diff --git a/docs/examples/quickstart/cythonize/htmlreport_pyx.png b/docs/examples/quickstart/cythonize/htmlreport_pyx.png new file mode 100755 index 000000000..bc9cff2f9 Binary files /dev/null and b/docs/examples/quickstart/cythonize/htmlreport_pyx.png differ diff --git a/docs/examples/quickstart/cythonize/integrate_cy.py b/docs/examples/quickstart/cythonize/integrate_cy.py new file mode 100644 index 000000000..592ce8db7 --- /dev/null +++ b/docs/examples/quickstart/cythonize/integrate_cy.py @@ -0,0 +1,13 @@ +def f(x: cython.double): + return x ** 2 - x + + +def integrate_f(a: cython.double, b: cython.double, N: cython.int): + i: cython.int + s: cython.double + dx: cython.double + s = 0 + dx = (b - a) / N + for i in range(N): + s += f(a + i * dx) + return s * dx diff --git a/docs/examples/quickstart/cythonize/integrate_cy.pyx b/docs/examples/quickstart/cythonize/integrate_cy.pyx index f676969d8..0e20a6c33 100644 --- a/docs/examples/quickstart/cythonize/integrate_cy.pyx +++ b/docs/examples/quickstart/cythonize/integrate_cy.pyx @@ -4,7 +4,8 @@ def f(double x): def integrate_f(double a, double b, int N): cdef int i - cdef double s, dx + cdef double s + cdef double dx s = 0 dx = (b - a) / N for i in range(N): diff --git a/docs/src/quickstart/build.rst b/docs/src/quickstart/build.rst index 5382bc663..5d9e8a307 100644 --- a/docs/src/quickstart/build.rst +++ b/docs/src/quickstart/build.rst @@ -3,7 +3,7 @@ Building Cython code Cython code must, unlike Python, be compiled. This happens in two stages: - - A ``.pyx`` file is compiled by Cython to a ``.c`` file, containing + - A ``.pyx`` or ``.py`` file is compiled by Cython to a ``.c`` file, containing the code of a Python extension module. - The ``.c`` file is compiled by a C compiler to a ``.so`` file (or ``.pyd`` on Windows) which can be @@ -73,14 +73,32 @@ and load the ``Cython`` extension from within the Jupyter notebook:: %load_ext Cython -Then, prefix a cell with the ``%%cython`` marker to compile it:: +Then, prefix a cell with the ``%%cython`` marker to compile it - %%cython +.. tabs:: - cdef int a = 0 - for i in range(10): - a += i - print(a) + .. group-tab:: Pure Python + + .. code-block:: python + + %%cython + + a: cython.int = 0 + for i in range(10): + a += i + print(a) + + + .. group-tab:: Cython + + .. code-block:: python + + %%cython + + cdef int a = 0 + for i in range(10): + a += i + print(a) You can show Cython's code analysis by passing the ``--annotate`` option:: diff --git a/docs/src/quickstart/cythonize.rst b/docs/src/quickstart/cythonize.rst index 22cad0470..09cbb470f 100644 --- a/docs/src/quickstart/cythonize.rst +++ b/docs/src/quickstart/cythonize.rst @@ -1,6 +1,9 @@ Faster code via static typing ============================= +.. include:: + ../two-syntax-variants-used + Cython is a Python compiler. This means that it can compile normal Python code without changes (with a few obvious exceptions of some as-yet unsupported language features, see :ref:`Cython limitations`). @@ -33,6 +36,7 @@ Typing Variables Consider the following pure Python code: .. literalinclude:: ../../examples/quickstart/cythonize/integrate.py + :caption: integrate.py Simply compiling this in Cython merely gives a 35% speedup. This is better than nothing, but adding some static types can make a much larger @@ -40,7 +44,17 @@ difference. With additional type declarations, this might look like: -.. literalinclude:: ../../examples/quickstart/cythonize/integrate_cy.pyx +.. tabs:: + + .. group-tab:: Pure Python + + .. literalinclude:: ../../examples/quickstart/cythonize/integrate_cy.py + :caption: integrate_cy.py + + .. group-tab:: Cython + + .. literalinclude:: ../../examples/quickstart/cythonize/integrate_cy.pyx + :caption: integrate_cy.pyx Since the iterator variable ``i`` is typed with C semantics, the for-loop will be compiled to pure C code. Typing ``a``, ``s`` and ``dx`` is important as they are involved @@ -55,27 +69,40 @@ Typing Functions Python function calls can be expensive -- in Cython doubly so because one might need to convert to and from Python objects to do the call. -In our example above, the argument is assumed to be a C double both inside f() +In our example above, the argument is assumed to be a C double both inside ``f()`` and in the call to it, yet a Python ``float`` object must be constructed around the argument in order to pass it. -Therefore Cython provides a syntax for declaring a C-style function, -the cdef keyword: +Therefore, Cython provides a way for declaring a C-style function, +the Cython specific ``cdef`` statement, as well as the ``@cfunc`` decorator to +declare C-style functions in Python syntax. Both approaches are +equivalent and produce the same C code: + +.. tabs:: + + .. group-tab:: Pure Python + + .. literalinclude:: ../../examples/quickstart/cythonize/cdef_keyword.py -.. literalinclude:: ../../examples/quickstart/cythonize/cdef_keyword.pyx + .. group-tab:: Cython + + .. literalinclude:: ../../examples/quickstart/cythonize/cdef_keyword.pyx Some form of except-modifier should usually be added, otherwise Cython will not be able to propagate exceptions raised in the function (or a function it calls). The ``except? -2`` means that an error will be checked for if ``-2`` is returned (though the ``?`` indicates that ``-2`` may also -be used as a valid return value). +be used as a valid return value). The same can be expressed using only Python +syntax with the decorator ``@exceptval(-2, check=True)``. + Alternatively, the slower ``except *`` is always safe. An except clause can be left out if the function returns a Python object or if it is guaranteed that an exception will not be raised -within the function call. +within the function call. Again, Cython provides the decorator ``@exceptval(check=True)`` +providing the same functionality. -A side-effect of cdef is that the function is no longer available from -Python-space, as Python wouldn't know how to call it. It is also no +A side-effect of ``cdef`` (and the ``@cfunc`` decorator) is that the function is no longer +visible from Python-space, as Python wouldn't know how to call it. It is also no longer possible to change :func:`f` at runtime. Using the ``cpdef`` keyword instead of ``cdef``, a Python wrapper is also @@ -84,7 +111,8 @@ typed values directly) and from Python (wrapping values in Python objects). In fact, ``cpdef`` does not just provide a Python wrapper, it also installs logic to allow the method to be overridden by python methods, even when called from within cython. This does add a tiny overhead compared to ``cdef`` -methods. +methods. Again, Cython provides a ``@ccall`` decorator which provides the same +functionality as ``cpdef`` keyword. Speedup: 150 times over pure Python. @@ -118,7 +146,15 @@ This report is invaluable when optimizing a function for speed, and for determining when to :ref:`release the GIL `: in general, a ``nogil`` block may contain only "white" code. -.. figure:: htmlreport.png +.. tabs:: + + .. group-tab:: Pure Python + + .. figure:: htmlreport_py.png + + .. group-tab:: Cython + + .. figure:: htmlreport_pyx.png Note that Cython deduces the type of local variables based on their assignments (including as loop variable targets) which can also cut down on the need to @@ -135,4 +171,3 @@ with this language feature. It can be of great help to cut down on the need to t everything, but it also can lead to surprises. Especially if one isn't familiar with arithmetic expressions with c types. A quick overview of those can be found `here `_. - diff --git a/docs/src/quickstart/htmlreport.png b/docs/src/quickstart/htmlreport.png deleted file mode 100644 index 0fc5e7bbe..000000000 Binary files a/docs/src/quickstart/htmlreport.png and /dev/null differ -- cgit v1.2.1