Saturday, December 18, 2010

Exploring Cython, baby steps

I'm looking at Cython. From the docs:
Cython’s two major use cases [are]: extending the CPython interpreter with fast binary modules, and interfacing Python code with external C libraries.

The idea is that

The source code gets translated into optimized C/C++ code and compiled as Python extension modules

The "Hello world" example works fine. I install Cython with easy_install. Following the tutorial, in hello.pyx I have:

def say_hello_to(name):
print("Hello %s!" % name)

I write another file setup.hello.py:

from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext

ext_modules = [Extension("hello", ["hello.pyx"])]

setup(
name = 'Hello world app',
cmdclass = {'build_ext': build_ext},
ext_modules = ext_modules
)

And I do this:

$ python setup.hello.py build_ext --inplace
running build_ext
cythoning hello.pyx to hello.c
building 'hello' extension
gcc-4.2 -fno-strict-aliasing -fno-common -dynamic -DNDEBUG -g -fwrapv -Os -Wall -Wstrict-prototypes -DENABLE_DTRACE -arch i386 -arch ppc -arch x86_64 -pipe -I/System/Library/Frameworks/Python.framework/Versions/2.6/include/python2.6 -c hello.c -o build/temp.macosx-10.6-universal-2.6/hello.o
gcc-4.2 -Wl,-F. -bundle -undefined dynamic_lookup -arch i386 -arch ppc -arch x86_64 build/temp.macosx-10.6-universal-2.6/hello.o -o hello.so

From the interpreter we can import functions from hello.so:

>>> from hello import say_hello_to
>>> say_hello_to('Tom')
Hello Tom!

Now, to do something a bit more interesting. Suppose I want to use the sqrt function from the C library declared in math.h. I write a file cy_script.pyx:

cdef extern from "math.h":
double sqrt(double)

cdef double f(int i):
return sqrt(i)

print f(2)

The print statement is there for testing, as you'll see. We write another file setup.py with this:

from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext

ext_modules=[
Extension("cy_script",
["cy_script.pyx"],
libraries=["m"]) # Unix-like specific
]

setup(
name = "Cy_script",
cmdclass = {"build_ext": build_ext},
ext_modules = ext_modules
)

We build it as before:

$ python setup.py build_ext --inplace
running build_ext
cythoning cy_script.pyx to cy_script.c
building 'cy_script' extension
gcc-4.2 -fno-strict-aliasing -fno-common -dynamic -DNDEBUG -g -fwrapv -Os -Wall -Wstrict-prototypes -DENABLE_DTRACE -arch i386 -arch ppc -arch x86_64 -pipe -I/System/Library/Frameworks/Python.framework/Versions/2.6/include/python2.6 -c cy_script.c -o build/temp.macosx-10.6-universal-2.6/cy_script.o
gcc-4.2 -Wl,-F. -bundle -undefined dynamic_lookup -arch i386 -arch ppc -arch x86_64 build/temp.macosx-10.6-universal-2.6/cy_script.o -lm -o cy_script.so

In the interpreter:

>>> import cy_script
1.41421356237
>>> cy_script.f(3)
Traceback (most recent call last):
File "", line 1, in
AttributeError: 'module' object has no attribute 'f'
>>> dir()
['__builtins__', '__doc__', '__name__', '__package__', 'cy_script']
>>> from cy_script import *
>>> dir()
['__builtins__', '__doc__', '__name__', '__package__', 'cy_script']

Notice that following the first statement we get execution of print f(2); that is, cy_script was actually imported, but the names defined in cy_script are not available to us.

I have to look into more to see how to do this. What's the difference between the first example and the second one?
[UPDATE: Answer from Stack Overflow, as always!
Substitute cpdef double f(int i):, and it'll work. ]

[UPDATE2: There's an even cooler way to do this:

>>> import pyximport
>>> pyximport.install()
>>> import hello
>>> hello.say_hello_to('Tom')
Hello Tom!
>>> import cy_script
1.41421356237
>>> cy_script.f(3)
1.7320508075688772

Details here. ]