Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pythonise the docs #4187: "Extension types (aka. cdef classes)" (cdef_classes.rst) #4232

Merged
merged 34 commits into from Jul 14, 2021
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
6d42486
tutorial\cdef_classes.rst: format: 4 spaces for indentation, ** for t…
0dminnimda Jun 15, 2021
8816e18
tutorial\cdef_classes.rst: return `literalinclude` and add it to tabs
0dminnimda Jun 15, 2021
5313374
Create cdef_classes\math_function_2.py
0dminnimda Jun 15, 2021
2a7ed76
cdef_classes\math_function_2.pyx: add blank lines for alignment
0dminnimda Jun 15, 2021
e203696
math_function_2.py: use cython.locals to match the cython version
0dminnimda Jun 16, 2021
e98d062
math_function_2.pyx: add blank lines for alignment
0dminnimda Jun 16, 2021
c2856c7
math_function_2.py: remove unnecessary line
0dminnimda Jun 17, 2021
1248b00
math_function_2.pyx: remove blank lines for alignment
0dminnimda Jun 17, 2021
ea6901c
Create sin_of_square.py
0dminnimda Jun 17, 2021
b6c1cf7
sin_of_square.pyx: add blank lines for alignment
0dminnimda Jun 17, 2021
0dcacdb
Create integrate.py
0dminnimda Jun 17, 2021
7de55df
integrate.pyx: add blank lines for alignment
0dminnimda Jun 17, 2021
8216f60
cdef_classes.rst: add tabs for sin_of_square
0dminnimda Jun 17, 2021
5956449
cdef_classes.rst: add caption for sin_of_square.pxd
0dminnimda Jun 17, 2021
ba43d04
cdef_classes.rst: add tabs for integrate
0dminnimda Jun 17, 2021
7752452
cdef_classes.rst: complete title border
0dminnimda Jun 17, 2021
f6e30c9
two-syntax-variants-used: add content to note, content does not change
0dminnimda Jun 18, 2021
3dfeea1
cdef_classes.rst: add two-syntax-variants-used
0dminnimda Jun 18, 2021
5913720
math_function_2.py & sin_of_square.py: locals -> annotations
0dminnimda Jun 19, 2021
e9dbabf
math_function_2.pyx & sin_of_square.pyx: remove blank lines for align…
0dminnimda Jun 19, 2021
64839b5
integrate.py: locals -> annotations
0dminnimda Jun 19, 2021
fc0b7c4
integrate.pyx: remove blank lines for alignment
0dminnimda Jun 19, 2021
805d077
integrate, math_function_2, sin_of_square: cython.double -> float
0dminnimda Jun 19, 2021
7218f1d
Create wave_function.py
0dminnimda Jun 19, 2021
bc60acc
wave_function.pyx: add blank lines for alignment
0dminnimda Jun 19, 2021
2c1cb03
cdef_classes.rst: add tabs for wave_function
0dminnimda Jun 19, 2021
3f54bc0
Create nonecheck.py
0dminnimda Jun 19, 2021
f3e07c7
nonecheck.pyx: add blank lines for alignment
0dminnimda Jun 19, 2021
3070b22
cdef_classes.rst: add tabs for nonecheck
0dminnimda Jun 19, 2021
d9c854e
cdef_classes/sin_of_square.py: remove unnecessary fallback
0dminnimda Jun 30, 2021
53df4fc
cdef_classes.rst: return text about pxd
0dminnimda Jun 30, 2021
847295a
sin_of_square.pyx: remove blank lines for alignment
0dminnimda Jun 30, 2021
e34d375
cdef_classes.rst: remove unnecessary whitespace
0dminnimda Jun 30, 2021
ef17f0c
cdef_classes: reduce indent in some places to 2
0dminnimda Jul 13, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
17 changes: 17 additions & 0 deletions docs/examples/tutorial/cdef_classes/integrate.py
@@ -0,0 +1,17 @@
from cython.cimports.sin_of_square import Function, SinOfSquareFunction

def integrate(f: Function, a: float, b: float, N: cython.int):
i: cython.int

if f is None:
raise ValueError("f cannot be None")

s: float = 0
dx: float = (b - a) / N

for i in range(N):
s += f.evaluate(a + i * dx)

return s * dx

print(integrate(SinOfSquareFunction(), 0, 1, 10000))
3 changes: 3 additions & 0 deletions docs/examples/tutorial/cdef_classes/integrate.pyx
Expand Up @@ -5,10 +5,13 @@ def integrate(Function f, double a, double b, int N):
cdef double s, dx
if f is None:
raise ValueError("f cannot be None")

s = 0
dx = (b - a) / N

for i in range(N):
s += f.evaluate(a + i * dx)

return s * dx

print(integrate(SinOfSquareFunction(), 0, 1, 10000))
5 changes: 5 additions & 0 deletions docs/examples/tutorial/cdef_classes/math_function_2.py
@@ -0,0 +1,5 @@
@cython.cclass
class Function:
@cython.ccall
def evaluate(self, x: float) -> float:
return 0
2 changes: 2 additions & 0 deletions docs/examples/tutorial/cdef_classes/math_function_2.pyx
@@ -1,3 +1,5 @@

cdef class Function:

cpdef double evaluate(self, double x) except *:
return 0
20 changes: 20 additions & 0 deletions docs/examples/tutorial/cdef_classes/nonecheck.py
@@ -0,0 +1,20 @@
# cython: nonecheck=True
# ^^^ Turns on nonecheck globally

import cython

@cython.cclass
class MyClass:
pass

# Turn off nonecheck locally for the function
@cython.nonecheck(False)
def func():
obj: MyClass = None
try:
# Turn nonecheck on again for a block
with cython.nonecheck(True):
print(obj.myfunc()) # Raises exception
except AttributeError:
pass
print(obj.myfunc()) # Hope for a crash!
1 change: 1 addition & 0 deletions docs/examples/tutorial/cdef_classes/nonecheck.pyx
Expand Up @@ -3,6 +3,7 @@

import cython


cdef class MyClass:
pass

Expand Down
16 changes: 16 additions & 0 deletions docs/examples/tutorial/cdef_classes/sin_of_square.py
@@ -0,0 +1,16 @@
if cython.compiled:
from cython.cimports.libc.math import sin
else:
from math import sin
0dminnimda marked this conversation as resolved.
Show resolved Hide resolved

@cython.cclass
class Function:
@cython.ccall
def evaluate(self, x: float) -> float:
return 0

@cython.cclass
class SinOfSquareFunction(Function):
@cython.ccall
def evaluate(self, x: float) -> float:
return sin(x ** 2)
7 changes: 7 additions & 0 deletions docs/examples/tutorial/cdef_classes/sin_of_square.pyx
@@ -1,9 +1,16 @@

from libc.math cimport sin




cdef class Function:

cpdef double evaluate(self, double x) except *:
return 0


cdef class SinOfSquareFunction(Function):

cpdef double evaluate(self, double x) except *:
return sin(x ** 2)
22 changes: 22 additions & 0 deletions docs/examples/tutorial/cdef_classes/wave_function.py
@@ -0,0 +1,22 @@
from cython.cimports.sin_of_square import Function

@cython.cclass
class WaveFunction(Function):

# Not available in Python-space:
offset: float

# Available in Python-space:
freq = cython.declare(cython.double, visibility='public')

# Available in Python-space, but only for reading:
scale = cython.declare(cython.double, visibility='readonly')

# Available in Python-space:
@property
def period(self):
return 1.0 / self.freq

@period.setter
def period(self, value):
self.freq = 1.0 / value
1 change: 1 addition & 0 deletions docs/examples/tutorial/cdef_classes/wave_function.pyx
@@ -1,5 +1,6 @@
from sin_of_square cimport Function


cdef class WaveFunction(Function):

# Not available in Python-space:
Expand Down
110 changes: 79 additions & 31 deletions docs/src/tutorial/cdef_classes.rst
@@ -1,5 +1,9 @@
***********************************
Extension types (aka. cdef classes)
===================================
***********************************

.. include::
../two-syntax-variants-used

To support object-oriented programming, Cython supports writing normal
Python classes exactly as in Python:
Expand All @@ -24,17 +28,33 @@ single inheritance. Normal Python classes, on the other hand, can
inherit from any number of Python classes and extension types, both in
Cython code and pure Python code.

.. tabs::
.. group-tab:: Pure Python

.. literalinclude:: ../../examples/tutorial/cdef_classes/math_function_2.py

.. group-tab:: Cython

.. literalinclude:: ../../examples/tutorial/cdef_classes/math_function_2.pyx

So far our integration example has not been very useful as it only
integrates a single hard-coded function. In order to remedy this,
with hardly sacrificing speed, we will use a cdef class to represent a
function on floating point numbers:

.. literalinclude:: ../../examples/tutorial/cdef_classes/math_function_2.pyx

The directive cpdef makes two versions of the method available; one
fast for use from Cython and one slower for use from Python. Then:

.. literalinclude:: ../../examples/tutorial/cdef_classes/sin_of_square.pyx
.. tabs::
.. group-tab:: Pure Python

.. literalinclude:: ../../examples/tutorial/cdef_classes/sin_of_square.py
:caption: sin_of_square.py

.. group-tab:: Cython

.. literalinclude:: ../../examples/tutorial/cdef_classes/sin_of_square.pyx
:caption: sin_of_square.pyx

This does slightly more than providing a python wrapper for a cdef
method: unlike a cdef method, a cpdef method is fully overridable by
Expand All @@ -43,25 +63,35 @@ little calling overhead compared to a cdef method.

To make the class definitions visible to other modules, and thus allow for
efficient C-level usage and inheritance outside of the module that
implements them, we define them in a :file:`sin_of_square.pxd` file:
implements them, we define them in:
0dminnimda marked this conversation as resolved.
Show resolved Hide resolved

.. literalinclude:: ../../examples/tutorial/cdef_classes/sin_of_square.pxd
:caption: sin_of_square.pxd

Using this, we can now change our integration example:

.. literalinclude:: ../../examples/tutorial/cdef_classes/integrate.pyx
.. tabs::
.. group-tab:: Pure Python

.. literalinclude:: ../../examples/tutorial/cdef_classes/integrate.py
:caption: integrate.py

.. group-tab:: Cython

.. literalinclude:: ../../examples/tutorial/cdef_classes/integrate.pyx
:caption: integrate.pyx

This is almost as fast as the previous code, however it is much more flexible
as the function to integrate can be changed. We can even pass in a new
function defined in Python-space::

>>> import integrate
>>> class MyPolynomial(integrate.Function):
... def evaluate(self, x):
... return 2*x*x + 3*x - 10
...
>>> integrate(MyPolynomial(), 0, 1, 10000)
-7.8335833300000077
>>> import integrate
>>> class MyPolynomial(integrate.Function):
... def evaluate(self, x):
... return 2*x*x + 3*x - 10
...
>>> integrate(MyPolynomial(), 0, 1, 10000)
-7.8335833300000077
scoder marked this conversation as resolved.
Show resolved Hide resolved

This is about 20 times slower, but still about 10 times faster than
the original Python-only integration code. This shows how large the
Expand All @@ -70,32 +100,50 @@ into a Cython module.

Some notes on our new implementation of ``evaluate``:

- The fast method dispatch here only works because ``evaluate`` was
declared in ``Function``. Had ``evaluate`` been introduced in
``SinOfSquareFunction``, the code would still work, but Cython
would have used the slower Python method dispatch mechanism
instead.
- The fast method dispatch here only works because ``evaluate`` was
declared in ``Function``. Had ``evaluate`` been introduced in
``SinOfSquareFunction``, the code would still work, but Cython
would have used the slower Python method dispatch mechanism
instead.

- In the same way, had the argument ``f`` not been typed, but only
been passed as a Python object, the slower Python dispatch would
be used.
- In the same way, had the argument ``f`` not been typed, but only
been passed as a Python object, the slower Python dispatch would
be used.

- Since the argument is typed, we need to check whether it is
``None``. In Python, this would have resulted in an ``AttributeError``
when the ``evaluate`` method was looked up, but Cython would instead
try to access the (incompatible) internal structure of ``None`` as if
it were a ``Function``, leading to a crash or data corruption.
- Since the argument is typed, we need to check whether it is
``None``. In Python, this would have resulted in an ``AttributeError``
when the ``evaluate`` method was looked up, but Cython would instead
try to access the (incompatible) internal structure of ``None`` as if
it were a ``Function``, leading to a crash or data corruption.

There is a *compiler directive* ``nonecheck`` which turns on checks
for this, at the cost of decreased speed. Here's how compiler directives
are used to dynamically switch on or off ``nonecheck``:

.. literalinclude:: ../../examples/tutorial/cdef_classes/nonecheck.pyx
.. tabs::
.. group-tab:: Pure Python

.. literalinclude:: ../../examples/tutorial/cdef_classes/nonecheck.py
:caption: nonecheck.py

.. group-tab:: Cython

.. literalinclude:: ../../examples/tutorial/cdef_classes/nonecheck.pyx
:caption: nonecheck.pyx

Attributes in cdef classes behave differently from attributes in regular classes:

- All attributes must be pre-declared at compile-time
- Attributes are by default only accessible from Cython (typed access)
- Properties can be declared to expose dynamic attributes to Python-space
- All attributes must be pre-declared at compile-time
- Attributes are by default only accessible from Cython (typed access)
- Properties can be declared to expose dynamic attributes to Python-space

.. tabs::
.. group-tab:: Pure Python

.. literalinclude:: ../../examples/tutorial/cdef_classes/wave_function.py
:caption: wave_function.py

.. group-tab:: Cython

.. literalinclude:: ../../examples/tutorial/cdef_classes/wave_function.pyx
.. literalinclude:: ../../examples/tutorial/cdef_classes/wave_function.pyx
:caption: wave_function.pyx
22 changes: 12 additions & 10 deletions docs/src/two-syntax-variants-used
@@ -1,13 +1,15 @@
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
.. note::

.. code-block:: python
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

import cython
.. code-block:: python

in the Python module that you want to compile.
import cython

in the Python module that you want to compile.