Using Cython To Link Python To A Shared Library
Sure !
(In the following, I assume that you already know how to deal with cimport
and the interactions between .pxd
and .pyx
. If this is not completely the case, just ask and I will develop that part as well)
The sample (grabbed from a C++ project of mine, but a C project would work pretty much the same) :
1. The Distutils setup file :
Assuming that the extension to be created will be called myext
and the 3rd party shared library is libexternlib.so
(note the lib* prefix, here)...
# setup.py fileimport sysimport osimport shutilfrom distutils.core import setupfrom distutils.extension import Extensionfrom Cython.Distutils import build_ext# clean previous buildfor root, dirs, files in os.walk(".", topdown=False): for name in files: if (name.startswith("myext") and not(name.endswith(".pyx") or name.endswith(".pxd"))): os.remove(os.path.join(root, name)) for name in dirs: if (name == "build"): shutil.rmtree(name)# build "myext.so" python extension to be added to "PYTHONPATH" afterwards...setup( cmdclass = {'build_ext': build_ext}, ext_modules = [ Extension("myext", sources=["myext.pyx", "SomeAdditionalCppClass1.cpp", "SomeAdditionalCppClass2.cpp" ], libraries=["externlib"], # refers to "libexternlib.so" language="c++", # remove this if C and not C++ extra_compile_args=["-fopenmp", "-O3"], extra_link_args=["-DSOME_DEFINE_OPT", "-L./some/extra/dependency/dir/"] ) ])
Note : Your external .so
file is linked via the libraries
option :
libraries=["externlib"] # Without the 'lib' prefix and the '.so' extension...
Note : the sources
option can be used to get some additional source files compiled.
Important : myext.pxd
(do not confound with .pyd
- Windows stuff) and myext.pyx
should be in the same directory. At compile time the definition file, if it exists, is processed first (more).
2. Then run it as follows :
After having changed directory to the one containing your myext.pxd
, your myext.pyx
, as well as the above setup.py
script :
# setup.sh# Make the "myext" Python Module ("myext.so")CC="gcc" \CXX="g++" \CFLAGS="-I./some/path/to/includes/ -I../../../DEPENDENCIES/python2.7/inc -I../../../DEPENDENCIES/gsl-1.15" \LDFLAGS="-L./some/path/to/externlib/" \ python setup.py build_ext --inplace
Where :
libexternlib.so
is assumed to be located at./some/path/to/externlib/
yourheader.h
is assumed to be located at./some/path/to/includes/
Note : CFLAGS
could also have been setup using the extra_compile_args
option :
extra_compile_args=["-I./some/path/to/includes/", "-fopenmp", "-O3"]
Note : LDFLAGS
could also have been setup using the extra_link_args
option :
extra_link_args=["-L./some/path/to/externlib/", "-DSOME_DEFINE_OPT", "-L./some/extra/dependency/dir/"]
Once distutils is done with the build, you get some new files, specially the myext.cpp
, myext.h
and most importantly, the myext.so
.
3. After that, you're good to go :
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:./some/path/to/externlib/export PYTHONPATH=$PYTHONPATH:./some/path/to/myext/# Run some script requiring "myext.so"python somescript.py
Where your freshly created Python extension can be imported by its name :
# somescript.pyimport myextfrom myext import PySomeFeature...
Note about Optimization : By default -O2
is used for compiling the extension, but this can be overloaded (see above setup where -O3
is specified).
Note about Cython paths : If Cython was installed in a custom directory, you might want to add it to your environment, before all :
PYTHONPATH=$PYTHONPATH:../../../DEPENDENCIES/Cython-0.18 export PYTHONPATH;PATH=$PATH:../../../DEPENDENCIES/Cython-0.18/bin; export PATH;
Well, hope I covered the main points...