In my project, I have a custom Python package where part of the functionality is implemented with Cython to accelerate the slowest parts of the code. The syntax is very close to regular Python (in fact, it can be identical except for some imports), but Cython-compiled .so
libraries allow direct access to low-level NumPy arrays.
The problem: Cython and NumPy need to be installed before setup.py
starts running.
Here's what setup.py
looks like:
import numpy
from Cython.Build import cythonize
from setuptools import setup, Extension
setup(
name=...,
version=...,
...
ext_modules=cythonize([
Extension('package.cython_code1', ['package/cython_code1.pyx']),
Extension('package.cython_code2', ['package/cython_code2.pyx']),
]),
include_dirs=[numpy.get_include()],
)
There are a few ways to handle installing Cython and NumPy dependencies before setup.py
execution:
Option 1: setup_requires
Starting with setuptools version 18.0 (released 2015-06-23), you can specify Cython in setup_requires
and use .pyx
modules directly as sources for setuptools.Extension
:
from setuptools import setup, Extension
setup(
...
setup_requires=[
'setuptools>=18.0', # Setuptools 18.0+ properly handles Cython extensions
'cython',
],
ext_modules=[
Extension('package.cython_code1', sources=['package/cython_code1.pyx']),
Extension('package.cython_code2', sources=['package/cython_code2.pyx']),
],
include_dirs=[numpy.get_include()],
)
Note: If your installation tool is outdated and includes Cython in setup_requires
, it may raise an ImportError like this:
ImportError: No module named Cython.Build
To address this, you can wrap the cythonize
function in a simple closure to delay the import:
try:
from Cython.Build import cythonize
except ImportError:
# Create a closure for deferred import
def cythonize(*args, **kwargs):
from Cython.Build import cythonize
return cythonize(*args, **kwargs)
With this, setup_requires
will process and install Cython first, allowing cythonize
to import successfully afterward.
Alternative
Another approach is to install Cython and NumPy through setuptools.dist
before running the setup script:
from setuptools import dist
dist.Distribution().fetch_build_eggs(['Cython>=0.15.1', 'numpy>=1.10'])
This ensures Cython and NumPy dependencies are fetched early and allows setup.py
to run without ImportErrors.