Building and Importing

Compiling the Module

The most common way to build CPython extension modules is to use setuptools and a setup.py file. We start with a normal setuptools setup.py file, for example:

from setuptools import setup, find_packages


setup(
    name='fib',
    version='0.1.0',
    packages=find_packages(),
    license='GPL-2',
    classifiers=[
        'Development Status :: 3 - Alpha',
        'License :: OSI Approved :: GNU General Public License v2 (GPLv2)',
        'Natural Language :: English',
        'Programming Language :: Python :: 3 :: Only',
        'Programming Language :: Python :: Implementation :: CPython',
    ],
)

This defines a new package called fib with version ‘0.1.0’ and some other metadata.

Note

In the classifiers we indicate:

Programming Language :: Python :: Implementation :: CPython

This indicates that our package requires CPython extension modules or implementation details and would not work on PyPy, Jython, or other Python implementations.

With the standard boilerplate in place we need to add the C extension specific parts. Start by importing the Extension type from setuptools:

from setuptools import setup, find_packages, Extension

Next, we need to add a list of all of the extension modules we need build to our call to setup:

setup(
    ...,  # the arguments from before
    ext_modules=[
        Extension(
            # the qualified name of the extension module to build
            'fib.fib',
            # the files to compile into our module relative to ``setup.py``
            ['fib/fib.c'],
        ),
    ],
)

The Extension class makes sure we get the correct CPython headers and flags which were used to build the CPython invoking setup.py. We can customize the build process with arguments to Extension, but the default is enough to get us started.

Building

Let’s make sure our build environment is working.

Run the following commands from the repo root:

$ cd exercises/fib
$ python setup.py build_ext --inplace

python setup.py build_ext --inplace uses the setup.py to build our extension modules. The --inplace flag moves the compiled shared objects into the source tree so that they are in the Python search path.

Importing and Using

Now that the modules are built and moved to the search path, we can try to import and use the code in a normal repl. From the fib directory startup ipython and try some of the following expressions:

In [1]: from fib import fib

In [2]: fib(1)
Out[2]: 1

In [3]: fib(2)
Out[3]: 1

In [4]: fib(3)
Out[4]: 2

In [5]: fib(10)
Out[5]: 55

Invalid Arguments

Think back to our definition of pyfib in C. We started with:

static PyObject *
pyfib(PyObject *self, PyObject *n)
{
    unsigned long as_unsigned_long = PyLong_AsUnsignedLong(n);
    /* ... */
}

What happens if n is not actually an int object? Try fib('a') or another non-int object.

If C-c doesn’t kill the session, you might need to use C-z and then $ kill %1.

Why Did this Hang?

When n is not an integer object, PyLong_AsUnsignedLong() raises an exception and returns (unsigned long) -1 which is UNSIGNED_LONG_MAX. We ignore the error and enter the Fibonacci function’s loop which tries computing the 18446744073709551615th Fibonacci number which will take a very long time.