XClose

COMP0233: Research Software Engineering With Python

Home
Menu

Cython

Cython can be viewed as an extension of Python where variables and functions are annotated with extra information, in particular types. The resulting Cython source code will be compiled into optimized C or C++ code, and thereby yielding substantial speed-up of slow Python code. In other words, Cython provides a way of writing Python with comparable performance to that of C/C++.

Start Coding in Cython

Cython code must, unlike Python, be compiled. This happens in the following stages:

  • The cython code in .pyx file will be translated to a C file.
  • The C file will be compiled by a C compiler into a shared library, which will be directly loaded into Python.

In a Jupyter notebook, everything is a lot easier. One needs only to load the Cython extension (%load_ext Cython) at the beginning and put %%cython mark in front of cells of Cython code. Cells with Cython mark will be treated as a .pyx code and consequently, compiled into C.

For details, please see Building Cython Code.

Pure python Mandelbrot set:

In [1]:
xmin = -1.5
ymin = -1.0
xmax = 0.5
ymax = 1.0
resolution = 300
xstep = (xmax - xmin) / resolution
ystep = (ymax - ymin) / resolution
xs = [(xmin + (xmax - xmin) * i / resolution) for i in range(resolution)]
ys = [(ymin + (ymax - ymin) * i / resolution) for i in range(resolution)]
In [2]:
def mandel(position, limit=50):
    value = position
    while abs(value) < 2:
        limit -= 1
        value = value**2 + position
        if limit < 0:
            return 0
    return limit

Compiled by Cython:

In [3]:
%load_ext Cython
---------------------------------------------------------------------------
ModuleNotFoundError                       Traceback (most recent call last)
Cell In[3], line 1
----> 1 get_ipython().run_line_magic('load_ext', 'Cython')

File /opt/hostedtoolcache/Python/3.12.5/x64/lib/python3.12/site-packages/IPython/core/interactiveshell.py:2480, in InteractiveShell.run_line_magic(self, magic_name, line, _stack_depth)
   2478     kwargs['local_ns'] = self.get_local_scope(stack_depth)
   2479 with self.builtin_trap:
-> 2480     result = fn(*args, **kwargs)
   2482 # The code below prevents the output from being displayed
   2483 # when using magics with decorator @output_can_be_silenced
   2484 # when the last Python token in the expression is a ';'.
   2485 if getattr(fn, magic.MAGIC_OUTPUT_CAN_BE_SILENCED, False):

File /opt/hostedtoolcache/Python/3.12.5/x64/lib/python3.12/site-packages/IPython/core/magics/extension.py:33, in ExtensionMagics.load_ext(self, module_str)
     31 if not module_str:
     32     raise UsageError('Missing module name.')
---> 33 res = self.shell.extension_manager.load_extension(module_str)
     35 if res == 'already loaded':
     36     print("The %s extension is already loaded. To reload it, use:" % module_str)

File /opt/hostedtoolcache/Python/3.12.5/x64/lib/python3.12/site-packages/IPython/core/extensions.py:62, in ExtensionManager.load_extension(self, module_str)
     55 """Load an IPython extension by its module name.
     56 
     57 Returns the string "already loaded" if the extension is already loaded,
     58 "no load function" if the module doesn't have a load_ipython_extension
     59 function, or None if it succeeded.
     60 """
     61 try:
---> 62     return self._load_extension(module_str)
     63 except ModuleNotFoundError:
     64     if module_str in BUILTINS_EXTS:

File /opt/hostedtoolcache/Python/3.12.5/x64/lib/python3.12/site-packages/IPython/core/extensions.py:79, in ExtensionManager._load_extension(self, module_str)
     77     mod = import_module(module_str)
     78 mod = sys.modules[module_str]
---> 79 if self._call_load_ipython_extension(mod):
     80     self.loaded.add(module_str)
     81 else:

File /opt/hostedtoolcache/Python/3.12.5/x64/lib/python3.12/site-packages/IPython/core/extensions.py:129, in ExtensionManager._call_load_ipython_extension(self, mod)
    127 def _call_load_ipython_extension(self, mod):
    128     if hasattr(mod, 'load_ipython_extension'):
--> 129         mod.load_ipython_extension(self.shell)
    130         return True

File /opt/hostedtoolcache/Python/3.12.5/x64/lib/python3.12/site-packages/Cython/__init__.py:11, in load_ipython_extension(ip)
      9 def load_ipython_extension(ip):
     10     """Load the extension in IPython."""
---> 11     from .Build.IpythonMagic import CythonMagics  # pylint: disable=cyclic-import
     12     ip.register_magics(CythonMagics)

File /opt/hostedtoolcache/Python/3.12.5/x64/lib/python3.12/site-packages/Cython/Build/IpythonMagic.py:55
     53 import time
     54 import copy
---> 55 import distutils.log
     56 import textwrap
     58 IO_ENCODING = sys.getfilesystemencoding()

ModuleNotFoundError: No module named 'distutils'
In [4]:
%%cython

def mandel_cython(position, limit=50):
    value = position
    while abs(value) < 2:
        limit -= 1
        value = value**2 + position
        if limit < 0:
            return 0
    return limit
UsageError: Cell magic `%%cython` not found.

Let's verify the result

In [5]:
from matplotlib import pyplot as plt
%matplotlib inline
f, axarr = plt.subplots(1, 2)
axarr[0].imshow([[mandel(complex(x, y)) for x in xs] for y in ys], interpolation='none')
axarr[0].set_title('Pure Python')
axarr[1].imshow([[mandel_cython(complex(x, y)) for x in xs] for y in ys], interpolation='none')
axarr[1].set_title('Cython')
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[5], line 6
      4 axarr[0].imshow([[mandel(complex(x, y)) for x in xs] for y in ys], interpolation='none')
      5 axarr[0].set_title('Pure Python')
----> 6 axarr[1].imshow([[mandel_cython(complex(x, y)) for x in xs] for y in ys], interpolation='none')
      7 axarr[1].set_title('Cython')

NameError: name 'mandel_cython' is not defined
No description has been provided for this image
In [6]:
%timeit [[mandel(complex(x,y)) for x in xs] for y in ys] # pure python
%timeit [[mandel_cython(complex(x,y)) for x in xs] for y in ys] # cython
432 ms ± 667 μs per loop (mean ± std. dev. of 7 runs, 1 loop each)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[6], line 2
      1 get_ipython().run_line_magic('timeit', '[[mandel(complex(x,y)) for x in xs] for y in ys] # pure python')
----> 2 get_ipython().run_line_magic('timeit', '[[mandel_cython(complex(x,y)) for x in xs] for y in ys] # cython')

File /opt/hostedtoolcache/Python/3.12.5/x64/lib/python3.12/site-packages/IPython/core/interactiveshell.py:2480, in InteractiveShell.run_line_magic(self, magic_name, line, _stack_depth)
   2478     kwargs['local_ns'] = self.get_local_scope(stack_depth)
   2479 with self.builtin_trap:
-> 2480     result = fn(*args, **kwargs)
   2482 # The code below prevents the output from being displayed
   2483 # when using magics with decorator @output_can_be_silenced
   2484 # when the last Python token in the expression is a ';'.
   2485 if getattr(fn, magic.MAGIC_OUTPUT_CAN_BE_SILENCED, False):

File /opt/hostedtoolcache/Python/3.12.5/x64/lib/python3.12/site-packages/IPython/core/magics/execution.py:1185, in ExecutionMagics.timeit(self, line, cell, local_ns)
   1183 for index in range(0, 10):
   1184     number = 10 ** index
-> 1185     time_number = timer.timeit(number)
   1186     if time_number >= 0.2:
   1187         break

File /opt/hostedtoolcache/Python/3.12.5/x64/lib/python3.12/site-packages/IPython/core/magics/execution.py:173, in Timer.timeit(self, number)
    171 gc.disable()
    172 try:
--> 173     timing = self.inner(it, self.timer)
    174 finally:
    175     if gcold:

File <magic-timeit>:1, in inner(_it, _timer)

NameError: name 'mandel_cython' is not defined

We have improved the performance of a factor of 1.5 by just using the Cython compiler, without changing the code!

Cython with C Types

But we can do better by telling Cython what C data type we would use in the code. Note we're not actually writing C, we're writing Python with C types.

typed variable

In [7]:
%%cython
def var_typed_mandel_cython(position, limit=50):
    cdef double complex value # typed variable
    value = position
    while abs(value) < 2:
        limit -= 1
        value = value**2 + position
        if limit < 0:
            return 0
    return limit
UsageError: Cell magic `%%cython` not found.

typed function + typed variable

In [8]:
%%cython
cpdef call_typed_mandel_cython(double complex position,
                               int limit=50): # typed function
    cdef double complex value # typed variable
    value = position
    while abs(value)<2:
        limit -= 1
        value = value**2 + position
        if limit < 0:
            return 0
    return limit
UsageError: Cell magic `%%cython` not found.

performance of one number:

In [9]:
# pure python
%timeit a = mandel(complex(0, 0)) 
8.92 μs ± 31.4 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)
In [10]:
# primitive cython
%timeit a = mandel_cython(complex(0, 0)) 
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[10], line 2
      1 # primitive cython
----> 2 get_ipython().run_line_magic('timeit', 'a = mandel_cython(complex(0, 0))')

File /opt/hostedtoolcache/Python/3.12.5/x64/lib/python3.12/site-packages/IPython/core/interactiveshell.py:2480, in InteractiveShell.run_line_magic(self, magic_name, line, _stack_depth)
   2478     kwargs['local_ns'] = self.get_local_scope(stack_depth)
   2479 with self.builtin_trap:
-> 2480     result = fn(*args, **kwargs)
   2482 # The code below prevents the output from being displayed
   2483 # when using magics with decorator @output_can_be_silenced
   2484 # when the last Python token in the expression is a ';'.
   2485 if getattr(fn, magic.MAGIC_OUTPUT_CAN_BE_SILENCED, False):

File /opt/hostedtoolcache/Python/3.12.5/x64/lib/python3.12/site-packages/IPython/core/magics/execution.py:1185, in ExecutionMagics.timeit(self, line, cell, local_ns)
   1183 for index in range(0, 10):
   1184     number = 10 ** index
-> 1185     time_number = timer.timeit(number)
   1186     if time_number >= 0.2:
   1187         break

File /opt/hostedtoolcache/Python/3.12.5/x64/lib/python3.12/site-packages/IPython/core/magics/execution.py:173, in Timer.timeit(self, number)
    171 gc.disable()
    172 try:
--> 173     timing = self.inner(it, self.timer)
    174 finally:
    175     if gcold:

File <magic-timeit>:1, in inner(_it, _timer)

NameError: name 'mandel_cython' is not defined
In [11]:
# cython with C type variable
%timeit a = var_typed_mandel_cython(complex(0, 0)) 
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[11], line 2
      1 # cython with C type variable
----> 2 get_ipython().run_line_magic('timeit', 'a = var_typed_mandel_cython(complex(0, 0))')

File /opt/hostedtoolcache/Python/3.12.5/x64/lib/python3.12/site-packages/IPython/core/interactiveshell.py:2480, in InteractiveShell.run_line_magic(self, magic_name, line, _stack_depth)
   2478     kwargs['local_ns'] = self.get_local_scope(stack_depth)
   2479 with self.builtin_trap:
-> 2480     result = fn(*args, **kwargs)
   2482 # The code below prevents the output from being displayed
   2483 # when using magics with decorator @output_can_be_silenced
   2484 # when the last Python token in the expression is a ';'.
   2485 if getattr(fn, magic.MAGIC_OUTPUT_CAN_BE_SILENCED, False):

File /opt/hostedtoolcache/Python/3.12.5/x64/lib/python3.12/site-packages/IPython/core/magics/execution.py:1185, in ExecutionMagics.timeit(self, line, cell, local_ns)
   1183 for index in range(0, 10):
   1184     number = 10 ** index
-> 1185     time_number = timer.timeit(number)
   1186     if time_number >= 0.2:
   1187         break

File /opt/hostedtoolcache/Python/3.12.5/x64/lib/python3.12/site-packages/IPython/core/magics/execution.py:173, in Timer.timeit(self, number)
    171 gc.disable()
    172 try:
--> 173     timing = self.inner(it, self.timer)
    174 finally:
    175     if gcold:

File <magic-timeit>:1, in inner(_it, _timer)

NameError: name 'var_typed_mandel_cython' is not defined
In [12]:
# cython with typed variable + function
%timeit a = call_typed_mandel_cython(complex(0, 0))
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[12], line 2
      1 # cython with typed variable + function
----> 2 get_ipython().run_line_magic('timeit', 'a = call_typed_mandel_cython(complex(0, 0))')

File /opt/hostedtoolcache/Python/3.12.5/x64/lib/python3.12/site-packages/IPython/core/interactiveshell.py:2480, in InteractiveShell.run_line_magic(self, magic_name, line, _stack_depth)
   2478     kwargs['local_ns'] = self.get_local_scope(stack_depth)
   2479 with self.builtin_trap:
-> 2480     result = fn(*args, **kwargs)
   2482 # The code below prevents the output from being displayed
   2483 # when using magics with decorator @output_can_be_silenced
   2484 # when the last Python token in the expression is a ';'.
   2485 if getattr(fn, magic.MAGIC_OUTPUT_CAN_BE_SILENCED, False):

File /opt/hostedtoolcache/Python/3.12.5/x64/lib/python3.12/site-packages/IPython/core/magics/execution.py:1185, in ExecutionMagics.timeit(self, line, cell, local_ns)
   1183 for index in range(0, 10):
   1184     number = 10 ** index
-> 1185     time_number = timer.timeit(number)
   1186     if time_number >= 0.2:
   1187         break

File /opt/hostedtoolcache/Python/3.12.5/x64/lib/python3.12/site-packages/IPython/core/magics/execution.py:173, in Timer.timeit(self, number)
    171 gc.disable()
    172 try:
--> 173     timing = self.inner(it, self.timer)
    174 finally:
    175     if gcold:

File <magic-timeit>:1, in inner(_it, _timer)

NameError: name 'call_typed_mandel_cython' is not defined

Cython with numpy ndarray

You can use NumPy from Cython exactly the same as in regular Python, but by doing so you are losing potentially high speedups because Cython has support for fast access to NumPy arrays.

In [13]:
import numpy as np
ymatrix, xmatrix = np.mgrid[ymin:ymax:ystep, xmin:xmax:xstep]
values = xmatrix + 1j * ymatrix
In [14]:
%%cython
import numpy as np
cimport numpy as np 

cpdef numpy_cython_1(np.ndarray[double complex, ndim=2] position, 
                     int limit=50): 
    cdef np.ndarray[long,ndim=2] diverged_at
    cdef double complex value
    cdef int xlim
    cdef int ylim
    cdef double complex pos
    cdef int steps
    cdef int x, y

    xlim = position.shape[1]
    ylim = position.shape[0]
    diverged_at = np.zeros([ylim, xlim], dtype=int)
    for x in xrange(xlim):
        for y in xrange(ylim):
            steps = limit
            value = position[y,x]
            pos = position[y,x]
            while abs(value) < 2 and steps >= 0:
                steps -= 1
                value = value**2 + pos
            diverged_at[y,x] = steps
  
    return diverged_at
UsageError: Cell magic `%%cython` not found.

Note the double import of numpy: the standard numpy module and a Cython-enabled version of numpy that ensures fast indexing of and other operations on arrays. Both import statements are necessary in code that uses numpy arrays. The new thing in the code above is declaration of arrays by np.ndarray.

In [15]:
%timeit data_cy = [[mandel(complex(x,y)) for x in xs] for y in ys] # pure python
430 ms ± 315 μs per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [16]:
%timeit data_cy = [[call_typed_mandel_cython(complex(x,y)) for x in xs] for y in ys] # typed cython
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[16], line 1
----> 1 get_ipython().run_line_magic('timeit', 'data_cy = [[call_typed_mandel_cython(complex(x,y)) for x in xs] for y in ys] # typed cython')

File /opt/hostedtoolcache/Python/3.12.5/x64/lib/python3.12/site-packages/IPython/core/interactiveshell.py:2480, in InteractiveShell.run_line_magic(self, magic_name, line, _stack_depth)
   2478     kwargs['local_ns'] = self.get_local_scope(stack_depth)
   2479 with self.builtin_trap:
-> 2480     result = fn(*args, **kwargs)
   2482 # The code below prevents the output from being displayed
   2483 # when using magics with decorator @output_can_be_silenced
   2484 # when the last Python token in the expression is a ';'.
   2485 if getattr(fn, magic.MAGIC_OUTPUT_CAN_BE_SILENCED, False):

File /opt/hostedtoolcache/Python/3.12.5/x64/lib/python3.12/site-packages/IPython/core/magics/execution.py:1185, in ExecutionMagics.timeit(self, line, cell, local_ns)
   1183 for index in range(0, 10):
   1184     number = 10 ** index
-> 1185     time_number = timer.timeit(number)
   1186     if time_number >= 0.2:
   1187         break

File /opt/hostedtoolcache/Python/3.12.5/x64/lib/python3.12/site-packages/IPython/core/magics/execution.py:173, in Timer.timeit(self, number)
    171 gc.disable()
    172 try:
--> 173     timing = self.inner(it, self.timer)
    174 finally:
    175     if gcold:

File <magic-timeit>:1, in inner(_it, _timer)

NameError: name 'call_typed_mandel_cython' is not defined
In [17]:
%timeit numpy_cython_1(values) # ndarray
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[17], line 1
----> 1 get_ipython().run_line_magic('timeit', 'numpy_cython_1(values) # ndarray')

File /opt/hostedtoolcache/Python/3.12.5/x64/lib/python3.12/site-packages/IPython/core/interactiveshell.py:2480, in InteractiveShell.run_line_magic(self, magic_name, line, _stack_depth)
   2478     kwargs['local_ns'] = self.get_local_scope(stack_depth)
   2479 with self.builtin_trap:
-> 2480     result = fn(*args, **kwargs)
   2482 # The code below prevents the output from being displayed
   2483 # when using magics with decorator @output_can_be_silenced
   2484 # when the last Python token in the expression is a ';'.
   2485 if getattr(fn, magic.MAGIC_OUTPUT_CAN_BE_SILENCED, False):

File /opt/hostedtoolcache/Python/3.12.5/x64/lib/python3.12/site-packages/IPython/core/magics/execution.py:1185, in ExecutionMagics.timeit(self, line, cell, local_ns)
   1183 for index in range(0, 10):
   1184     number = 10 ** index
-> 1185     time_number = timer.timeit(number)
   1186     if time_number >= 0.2:
   1187         break

File /opt/hostedtoolcache/Python/3.12.5/x64/lib/python3.12/site-packages/IPython/core/magics/execution.py:173, in Timer.timeit(self, number)
    171 gc.disable()
    172 try:
--> 173     timing = self.inner(it, self.timer)
    174 finally:
    175     if gcold:

File <magic-timeit>:1, in inner(_it, _timer)

NameError: name 'numpy_cython_1' is not defined

A trick of using np.vectorize

In [18]:
numpy_cython_2 = np.vectorize(call_typed_mandel_cython)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[18], line 1
----> 1 numpy_cython_2 = np.vectorize(call_typed_mandel_cython)

NameError: name 'call_typed_mandel_cython' is not defined
In [19]:
%timeit numpy_cython_2(values) #  vectorize
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[19], line 1
----> 1 get_ipython().run_line_magic('timeit', 'numpy_cython_2(values) #  vectorize')

File /opt/hostedtoolcache/Python/3.12.5/x64/lib/python3.12/site-packages/IPython/core/interactiveshell.py:2480, in InteractiveShell.run_line_magic(self, magic_name, line, _stack_depth)
   2478     kwargs['local_ns'] = self.get_local_scope(stack_depth)
   2479 with self.builtin_trap:
-> 2480     result = fn(*args, **kwargs)
   2482 # The code below prevents the output from being displayed
   2483 # when using magics with decorator @output_can_be_silenced
   2484 # when the last Python token in the expression is a ';'.
   2485 if getattr(fn, magic.MAGIC_OUTPUT_CAN_BE_SILENCED, False):

File /opt/hostedtoolcache/Python/3.12.5/x64/lib/python3.12/site-packages/IPython/core/magics/execution.py:1185, in ExecutionMagics.timeit(self, line, cell, local_ns)
   1183 for index in range(0, 10):
   1184     number = 10 ** index
-> 1185     time_number = timer.timeit(number)
   1186     if time_number >= 0.2:
   1187         break

File /opt/hostedtoolcache/Python/3.12.5/x64/lib/python3.12/site-packages/IPython/core/magics/execution.py:173, in Timer.timeit(self, number)
    171 gc.disable()
    172 try:
--> 173     timing = self.inner(it, self.timer)
    174 finally:
    175     if gcold:

File <magic-timeit>:1, in inner(_it, _timer)

NameError: name 'numpy_cython_2' is not defined

Calling C functions from Cython

Example: compare sin() from Python and C library

In [20]:
%%cython
import math
cpdef py_sin():
    cdef int x
    cdef double y
    for x in range(1e7):
        y = math.sin(x)
UsageError: Cell magic `%%cython` not found.
In [21]:
%%cython
from libc.math cimport sin as csin # import from C library
cpdef c_sin():
    cdef int x
    cdef double y
    for x in range(1e7):
        y = csin(x)
UsageError: Cell magic `%%cython` not found.
In [22]:
%timeit [math.sin(i) for i in range(int(1e7))] # python
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[22], line 1
----> 1 get_ipython().run_line_magic('timeit', '[math.sin(i) for i in range(int(1e7))] # python')

File /opt/hostedtoolcache/Python/3.12.5/x64/lib/python3.12/site-packages/IPython/core/interactiveshell.py:2480, in InteractiveShell.run_line_magic(self, magic_name, line, _stack_depth)
   2478     kwargs['local_ns'] = self.get_local_scope(stack_depth)
   2479 with self.builtin_trap:
-> 2480     result = fn(*args, **kwargs)
   2482 # The code below prevents the output from being displayed
   2483 # when using magics with decorator @output_can_be_silenced
   2484 # when the last Python token in the expression is a ';'.
   2485 if getattr(fn, magic.MAGIC_OUTPUT_CAN_BE_SILENCED, False):

File /opt/hostedtoolcache/Python/3.12.5/x64/lib/python3.12/site-packages/IPython/core/magics/execution.py:1185, in ExecutionMagics.timeit(self, line, cell, local_ns)
   1183 for index in range(0, 10):
   1184     number = 10 ** index
-> 1185     time_number = timer.timeit(number)
   1186     if time_number >= 0.2:
   1187         break

File /opt/hostedtoolcache/Python/3.12.5/x64/lib/python3.12/site-packages/IPython/core/magics/execution.py:173, in Timer.timeit(self, number)
    171 gc.disable()
    172 try:
--> 173     timing = self.inner(it, self.timer)
    174 finally:
    175     if gcold:

File <magic-timeit>:1, in inner(_it, _timer)

NameError: name 'math' is not defined
In [23]:
%timeit py_sin()                                # cython call python library
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[23], line 1
----> 1 get_ipython().run_line_magic('timeit', 'py_sin()                                # cython call python library')

File /opt/hostedtoolcache/Python/3.12.5/x64/lib/python3.12/site-packages/IPython/core/interactiveshell.py:2480, in InteractiveShell.run_line_magic(self, magic_name, line, _stack_depth)
   2478     kwargs['local_ns'] = self.get_local_scope(stack_depth)
   2479 with self.builtin_trap:
-> 2480     result = fn(*args, **kwargs)
   2482 # The code below prevents the output from being displayed
   2483 # when using magics with decorator @output_can_be_silenced
   2484 # when the last Python token in the expression is a ';'.
   2485 if getattr(fn, magic.MAGIC_OUTPUT_CAN_BE_SILENCED, False):

File /opt/hostedtoolcache/Python/3.12.5/x64/lib/python3.12/site-packages/IPython/core/magics/execution.py:1185, in ExecutionMagics.timeit(self, line, cell, local_ns)
   1183 for index in range(0, 10):
   1184     number = 10 ** index
-> 1185     time_number = timer.timeit(number)
   1186     if time_number >= 0.2:
   1187         break

File /opt/hostedtoolcache/Python/3.12.5/x64/lib/python3.12/site-packages/IPython/core/magics/execution.py:173, in Timer.timeit(self, number)
    171 gc.disable()
    172 try:
--> 173     timing = self.inner(it, self.timer)
    174 finally:
    175     if gcold:

File <magic-timeit>:1, in inner(_it, _timer)

NameError: name 'py_sin' is not defined
In [24]:
%timeit c_sin()                                 # cython call C library
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[24], line 1
----> 1 get_ipython().run_line_magic('timeit', 'c_sin()                                 # cython call C library')

File /opt/hostedtoolcache/Python/3.12.5/x64/lib/python3.12/site-packages/IPython/core/interactiveshell.py:2480, in InteractiveShell.run_line_magic(self, magic_name, line, _stack_depth)
   2478     kwargs['local_ns'] = self.get_local_scope(stack_depth)
   2479 with self.builtin_trap:
-> 2480     result = fn(*args, **kwargs)
   2482 # The code below prevents the output from being displayed
   2483 # when using magics with decorator @output_can_be_silenced
   2484 # when the last Python token in the expression is a ';'.
   2485 if getattr(fn, magic.MAGIC_OUTPUT_CAN_BE_SILENCED, False):

File /opt/hostedtoolcache/Python/3.12.5/x64/lib/python3.12/site-packages/IPython/core/magics/execution.py:1185, in ExecutionMagics.timeit(self, line, cell, local_ns)
   1183 for index in range(0, 10):
   1184     number = 10 ** index
-> 1185     time_number = timer.timeit(number)
   1186     if time_number >= 0.2:
   1187         break

File /opt/hostedtoolcache/Python/3.12.5/x64/lib/python3.12/site-packages/IPython/core/magics/execution.py:173, in Timer.timeit(self, number)
    171 gc.disable()
    172 try:
--> 173     timing = self.inner(it, self.timer)
    174 finally:
    175     if gcold:

File <magic-timeit>:1, in inner(_it, _timer)

NameError: name 'c_sin' is not defined