diff options
Diffstat (limited to 'doc/source/user/c-info.python-as-glue.rst')
-rw-r--r-- | doc/source/user/c-info.python-as-glue.rst | 1523 |
1 files changed, 1523 insertions, 0 deletions
diff --git a/doc/source/user/c-info.python-as-glue.rst b/doc/source/user/c-info.python-as-glue.rst new file mode 100644 index 000000000..0e0c73cd8 --- /dev/null +++ b/doc/source/user/c-info.python-as-glue.rst @@ -0,0 +1,1523 @@ +******************** +Using Python as glue +******************** + +| There is no conversation more boring than the one where everybody +| agrees. +| --- *Michel de Montaigne* + +| Duct tape is like the force. It has a light side, and a dark side, and +| it holds the universe together. +| --- *Carl Zwanzig* + +Many people like to say that Python is a fantastic glue language. +Hopefully, this Chapter will convince you that this is true. The first +adopters of Python for science were typically people who used it to +glue together large applicaton codes running on super-computers. Not +only was it much nicer to code in Python than in a shell script or +Perl, in addition, the ability to easily extend Python made it +relatively easy to create new classes and types specifically adapted +to the problems being solved. From the interactions of these early +contributors, Numeric emerged as an array-like object that could be +used to pass data between these applications. + +As Numeric has matured and developed into NumPy, people have been able +to write more code directly in NumPy. Often this code is fast-enough +for production use, but there are still times that there is a need to +access compiled code. Either to get that last bit of efficiency out of +the algorithm or to make it easier to access widely-available codes +written in C/C++ or Fortran. + +This chapter will review many of the tools that are available for the +purpose of accessing code written in other compiled languages. There +are many resources available for learning to call other compiled +libraries from Python and the purpose of this Chapter is not to make +you an expert. The main goal is to make you aware of some of the +possibilities so that you will know what to "Google" in order to learn more. + +The http://www.scipy.org website also contains a great deal of useful +information about many of these tools. For example, there is a nice +description of using several of the tools explained in this chapter at +http://www.scipy.org/PerformancePython. This link provides several +ways to solve the same problem showing how to use and connect with +compiled code to get the best performance. In the process you can get +a taste for several of the approaches that will be discussed in this +chapter. + + +Calling other compiled libraries from Python +============================================ + +While Python is a great language and a pleasure to code in, its +dynamic nature results in overhead that can cause some code ( *i.e.* +raw computations inside of for loops) to be up 10-100 times slower +than equivalent code written in a static compiled language. In +addition, it can cause memory usage to be larger than necessary as +temporary arrays are created and destroyed during computation. For +many types of computing needs the extra slow-down and memory +consumption can often not be spared (at least for time- or memory- +critical portions of your code). Therefore one of the most common +needs is to call out from Python code to a fast, machine-code routine +(e.g. compiled using C/C++ or Fortran). The fact that this is +relatively easy to do is a big reason why Python is such an excellent +high-level language for scientific and engineering programming. + +Their are two basic approaches to calling compiled code: writing an +extension module that is then imported to Python using the import +command, or calling a shared-library subroutine directly from Python +using the ctypes module (included in the standard distribution with +Python 2.5). The first method is the most common (but with the +inclusion of ctypes into Python 2.5 this status may change). + +.. warning:: + + Calling C-code from Python can result in Python crashes if you are not + careful. None of the approaches in this chapter are immune. You have + to know something about the way data is handled by both NumPy and by + the third-party library being used. + + +Hand-generated wrappers +======================= + +Extension modules were discussed in Chapter `1 +<#sec-writing-an-extension>`__ . The most basic way to interface with +compiled code is to write an extension module and construct a module +method that calls the compiled code. For improved readability, your +method should take advantage of the PyArg_ParseTuple call to convert +between Python objects and C data-types. For standard C data-types +there is probably already a built-in converter. For others you may +need to write your own converter and use the "O&" format string which +allows you to specify a function that will be used to perform the +conversion from the Python object to whatever C-structures are needed. + +Once the conversions to the appropriate C-structures and C data-types +have been performed, the next step in the wrapper is to call the +underlying function. This is straightforward if the underlying +function is in C or C++. However, in order to call Fortran code you +must be familiar with how Fortran subroutines are called from C/C++ +using your compiler and platform. This can vary somewhat platforms and +compilers (which is another reason f2py makes life much simpler for +interfacing Fortran code) but generally involves underscore mangling +of the name and the fact that all variables are passed by reference +(i.e. all arguments are pointers). + +The advantage of the hand-generated wrapper is that you have complete +control over how the C-library gets used and called which can lead to +a lean and tight interface with minimal over-head. The disadvantage is +that you have to write, debug, and maintain C-code, although most of +it can be adapted using the time-honored technique of +"cutting-pasting-and-modifying" from other extension modules. Because, +the procedure of calling out to additional C-code is fairly +regimented, code-generation procedures have been developed to make +this process easier. One of these code- generation techniques is +distributed with NumPy and allows easy integration with Fortran and +(simple) C code. This package, f2py, will be covered briefly in the +next session. + + +f2py +==== + +F2py allows you to automatically construct an extension module that +interfaces to routines in Fortran 77/90/95 code. It has the ability to +parse Fortran 77/90/95 code and automatically generate Python +signatures for the subroutines it encounters, or you can guide how the +subroutine interfaces with Python by constructing an interface- +defintion-file (or modifying the f2py-produced one). + +.. index:: + single: f2py + +Creating source for a basic extension module +-------------------------------------------- + +Probably the easiest way to introduce f2py is to offer a simple +example. Here is one of the subroutines contained in a file named +:file:`add.f`: + +.. code-block:: none + + C + SUBROUTINE ZADD(A,B,C,N) + C + DOUBLE COMPLEX A(*) + DOUBLE COMPLEX B(*) + DOUBLE COMPLEX C(*) + INTEGER N + DO 20 J = 1, N + C(J) = A(J)+B(J) + 20 CONTINUE + END + +This routine simply adds the elements in two contiguous arrays and +places the result in a third. The memory for all three arrays must be +provided by the calling routine. A very basic interface to this +routine can be automatically generated by f2py:: + + f2py -m add add.f + +You should be able to run this command assuming your search-path is +set-up properly. This command will produce an extension module named +addmodule.c in the current directory. This extension module can now be +compiled and used from Python just like any other extension module. + + +Creating a compiled extension module +------------------------------------ + +You can also get f2py to compile add.f and also compile its produced +extension module leaving only a shared-library extension file that can +be imported from Python:: + + f2py -c -m add add.f + +This command leaves a file named add.{ext} in the current directory +(where {ext} is the appropriate extension for a python extension +module on your platform --- so, pyd, *etc.* ). This module may then be +imported from Python. It will contain a method for each subroutin in +add (zadd, cadd, dadd, sadd). The docstring of each method contains +information about how the module method may be called: + + >>> import add + >>> print add.zadd.__doc__ + zadd - Function signature: + zadd(a,b,c,n) + Required arguments: + a : input rank-1 array('D') with bounds (*) + b : input rank-1 array('D') with bounds (*) + c : input rank-1 array('D') with bounds (*) + n : input int + + +Improving the basic interface +----------------------------- + +The default interface is a very literal translation of the fortran +code into Python. The Fortran array arguments must now be NumPy arrays +and the integer argument should be an integer. The interface will +attempt to convert all arguments to their required types (and shapes) +and issue an error if unsuccessful. However, because it knows nothing +about the semantics of the arguments (such that C is an output and n +should really match the array sizes), it is possible to abuse this +function in ways that can cause Python to crash. For example: + + >>> add.zadd([1,2,3],[1,2],[3,4],1000) + +will cause a program crash on most systems. Under the covers, the +lists are being converted to proper arrays but then the underlying add +loop is told to cycle way beyond the borders of the allocated memory. + +In order to improve the interface, directives should be provided. This +is accomplished by constructing an interface definition file. It is +usually best to start from the interface file that f2py can produce +(where it gets its default behavior from). To get f2py to generate the +interface file use the -h option:: + + f2py -h add.pyf -m add add.f + +This command leaves the file add.pyf in the current directory. The +section of this file corresponding to zadd is: + +.. code-block:: none + + subroutine zadd(a,b,c,n) ! in :add:add.f + double complex dimension(*) :: a + double complex dimension(*) :: b + double complex dimension(*) :: c + integer :: n + end subroutine zadd + +By placing intent directives and checking code, the interface can be +cleaned up quite a bit until the Python module method is both easier +to use and more robust. + +.. code-block:: none + + subroutine zadd(a,b,c,n) ! in :add:add.f + double complex dimension(n) :: a + double complex dimension(n) :: b + double complex intent(out),dimension(n) :: c + integer intent(hide),depend(a) :: n=len(a) + end subroutine zadd + +The intent directive, intent(out) is used to tell f2py that ``c`` is +an output variable and should be created by the interface before being +passed to the underlying code. The intent(hide) directive tells f2py +to not allow the user to specify the variable, ``n``, but instead to +get it from the size of ``a``. The depend( ``a`` ) directive is +necessary to tell f2py that the value of n depends on the input a (so +that it won't try to create the variable n until the variable a is +created). + +The new interface has docstring: + + >>> print add.zadd.__doc__ + zadd - Function signature: + c = zadd(a,b) + Required arguments: + a : input rank-1 array('D') with bounds (n) + b : input rank-1 array('D') with bounds (n) + Return objects: + c : rank-1 array('D') with bounds (n) + +Now, the function can be called in a much more robust way: + + >>> add.zadd([1,2,3],[4,5,6]) + array([ 5.+0.j, 7.+0.j, 9.+0.j]) + +Notice the automatic conversion to the correct format that occurred. + + +Inserting directives in Fortran source +-------------------------------------- + +The nice interface can also be generated automatically by placing the +variable directives as special comments in the original fortran code. +Thus, if I modify the source code to contain: + +.. code-block:: none + + C + SUBROUTINE ZADD(A,B,C,N) + C + CF2PY INTENT(OUT) :: C + CF2PY INTENT(HIDE) :: N + CF2PY DOUBLE COMPLEX :: A(N) + CF2PY DOUBLE COMPLEX :: B(N) + CF2PY DOUBLE COMPLEX :: C(N) + DOUBLE COMPLEX A(*) + DOUBLE COMPLEX B(*) + DOUBLE COMPLEX C(*) + INTEGER N + DO 20 J = 1, N + C(J) = A(J) + B(J) + 20 CONTINUE + END + +Then, I can compile the extension module using:: + + f2py -c -m add add.f + +The resulting signature for the function add.zadd is exactly the same +one that was created previously. If the original source code had +contained A(N) instead of A(\*) and so forth with B and C, then I +could obtain (nearly) the same interface simply by placing the +INTENT(OUT) :: C comment line in the source code. The only difference +is that N would be an optional input that would default to the length +of A. + + +A filtering example +------------------- + +For comparison with the other methods to be discussed. Here is another +example of a function that filters a two-dimensional array of double +precision floating-point numbers using a fixed averaging filter. The +advantage of using Fortran to index into multi-dimensional arrays +should be clear from this example. + +.. code-block:: none + + SUBROUTINE DFILTER2D(A,B,M,N) + C + DOUBLE PRECISION A(M,N) + DOUBLE PRECISION B(M,N) + INTEGER N, M + CF2PY INTENT(OUT) :: B + CF2PY INTENT(HIDE) :: N + CF2PY INTENT(HIDE) :: M + DO 20 I = 2,M-1 + DO 40 J=2,N-1 + B(I,J) = A(I,J) + + $ (A(I-1,J)+A(I+1,J) + + $ A(I,J-1)+A(I,J+1) )*0.5D0 + + $ (A(I-1,J-1) + A(I-1,J+1) + + $ A(I+1,J-1) + A(I+1,J+1))*0.25D0 + 40 CONTINUE + 20 CONTINUE + END + +This code can be compiled and linked into an extension module named +filter using:: + + f2py -c -m filter filter.f + +This will produce an extension module named filter.so in the current +directory with a method named dfilter2d that returns a filtered +version of the input. + + +Calling f2py from Python +------------------------ + +The f2py program is written in Python and can be run from inside your +module. This provides a facility that is somewhat similar to the use +of weave.ext_tools described below. An example of the final interface +executed using Python code is: + +.. code-block:: python + + import numpy.f2py as f2py + fid = open('add.f') + source = fid.read() + fid.close() + f2py.compile(source, modulename='add') + import add + +The source string can be any valid Fortran code. If you want to save +the extension-module source code then a suitable file-name can be +provided by the source_fn keyword to the compile function. + + +Automatic extension module generation +------------------------------------- + +If you want to distribute your f2py extension module, then you only +need to include the .pyf file and the Fortran code. The distutils +extensions in NumPy allow you to define an extension module entirely +in terms of this interface file. A valid setup.py file allowing +distribution of the add.f module (as part of the package f2py_examples +so that it would be loaded as f2py_examples.add) is: + +.. code-block:: python + + def configuration(parent_package='', top_path=None) + from numpy.distutils.misc_util import Configuration + config = Configuration('f2py_examples',parent_package, top_path) + config.add_extension('add', sources=['add.pyf','add.f']) + return config + + if __name__ == '__main__': + from numpy.distutils.core import setup + setup(**configuration(top_path='').todict()) + +Installation of the new package is easy using:: + + python setup.py install + +assuming you have the proper permissions to write to the main site- +packages directory for the version of Python you are using. For the +resulting package to work, you need to create a file named __init__.py +(in the same directory as add.pyf). Notice the extension module is +defined entirely in terms of the "add.pyf" and "add.f" files. The +conversion of the .pyf file to a .c file is handled by numpy.disutils. + + +Conclusion +---------- + +The interface definition file (.pyf) is how you can fine-tune the +interface between Python and Fortran. There is decent documentation +for f2py found in the numpy/f2py/docs directory where-ever NumPy is +installed on your system (usually under site-packages). There is also +more information on using f2py (including how to use it to wrap C +codes) at http://www.scipy.org/Cookbook under the "Using NumPy with +Other Languages" heading. + +The f2py method of linking compiled code is currently the most +sophisticated and integrated approach. It allows clean separation of +Python with compiled code while still allowing for separate +distribution of the extension module. The only draw-back is that it +requires the existence of a Fortran compiler in order for a user to +install the code. However, with the existence of the free-compilers +g77, gfortran, and g95, as well as high-quality commerical compilers, +this restriction is not particularly onerous. In my opinion, Fortran +is still the easiest way to write fast and clear code for scientific +computing. It handles complex numbers, and multi-dimensional indexing +in the most straightforward way. Be aware, however, that some Fortran +compilers will not be able to optimize code as well as good hand- +written C-code. + +.. index:: + single: f2py + + +weave +===== + +Weave is a scipy package that can be used to automate the process of +extending Python with C/C++ code. It can be used to speed up +evaluation of an array expression that would otherwise create +temporary variables, to directly "inline" C/C++ code into Python, or +to create a fully-named extension module. You must either install +scipy or get the weave package separately and install it using the +standard python setup.py install. You must also have a C/C++-compiler +installed and useable by Python distutils in order to use weave. + +.. index:: + single: weave + +Somewhat dated, but still useful documentation for weave can be found +at the link http://www.scipy/Weave. There are also many examples found +in the examples directory which is installed under the weave directory +in the place where weave is installed on your system. + + +Speed up code involving arrays (also see scipy.numexpr) +------------------------------------------------------- + +This is the easiest way to use weave and requires minimal changes to +your Python code. It involves placing quotes around the expression of +interest and calling weave.blitz. Weave will parse the code and +generate C++ code using Blitz C++ arrays. It will then compile the +code and catalog the shared library so that the next time this exact +string is asked for (and the array types are the same), the already- +compiled shared library will be loaded and used. Because Blitz makes +extensive use of C++ templating, it can take a long time to compile +the first time. After that, however, the code should evaluate more +quickly than the equivalent NumPy expression. This is especially true +if your array sizes are large and the expression would require NumPy +to create several temporaries. Only expressions involving basic +arithmetic operations and basic array slicing can be converted to +Blitz C++ code. + +For example, consider the expression:: + + d = 4*a + 5*a*b + 6*b*c + +where a, b, and c are all arrays of the same type and shape. When the +data-type is double-precision and the size is 1000x1000, this +expression takes about 0.5 seconds to compute on an 1.1Ghz AMD Athlon +machine. When this expression is executed instead using blitz: + +.. code-block:: python + + d = empty(a.shape, 'd'); weave.blitz(expr) + +execution time is only about 0.20 seconds (about 0.14 seconds spent in +weave and the rest in allocating space for d). Thus, we've sped up the +code by a factor of 2 using only a simnple command (weave.blitz). Your +mileage may vary, but factors of 2-8 speed-ups are possible with this +very simple technique. + +If you are interested in using weave in this way, then you should also +look at scipy.numexpr which is another similar way to speed up +expressions by eliminating the need for temporary variables. Using +numexpr does not require a C/C++ compiler. + + +Inline C-code +------------- + +Probably the most widely-used method of employing weave is to +"in-line" C/C++ code into Python in order to speed up a time-critical +section of Python code. In this method of using weave, you define a +string containing useful C-code and then pass it to the function +**weave.inline** ( ``code_string``, ``variables`` ), where +code_string is a string of valid C/C++ code and variables is a list of +variables that should be passed in from Python. The C/C++ code should +refer to the variables with the same names as they are defined with in +Python. If weave.line should return anything the the special value +return_val should be set to whatever object should be returned. The +following example shows how to use weave on basic Python objects: + +.. code-block:: python + + code = r""" + int i; + py::tuple results(2); + for (i=0; i<a.length(); i++) { + a[i] = i; + } + results[0] = 3.0; + results[1] = 4.0; + return_val = results; + """ + a = [None]*10 + res = weave.inline(code,['a']) + +The C++ code shown in the code string uses the name 'a' to refer to +the Python list that is passed in. Because the Python List is a +mutable type, the elements of the list itself are modified by the C++ +code. A set of C++ classes are used to access Python objects using +simple syntax. + +The main advantage of using C-code, however, is to speed up processing +on an array of data. Accessing a NumPy array in C++ code using weave, +depends on what kind of type converter is chosen in going from NumPy +arrays to C++ code. The default converter creates 5 variables for the +C-code for every NumPy array passed in to weave.inline. The following +table shows these variables which can all be used in the C++ code. The +table assumes that ``myvar`` is the name of the array in Python with +data-type {dtype} (i.e. float64, float32, int8, etc.) + +=========== ============== ========================================= +Variable Type Contents +=========== ============== ========================================= +myvar {dtype}* Pointer to the first element of the array +Nmyvar npy_intp* A pointer to the dimensions array +Smyvar npy_intp* A pointer to the strides array +Dmyvar int The number of dimensions +myvar_array PyArrayObject* The entire structure for the array +=========== ============== ========================================= + +The in-lined code can contain references to any of these variables as +well as to the standard macros MYVAR1(i), MYVAR2(i,j), MYVAR3(i,j,k), +and MYVAR4(i,j,k,l). These name-based macros (they are the Python name +capitalized followed by the number of dimensions needed) will de- +reference the memory for the array at the given location with no error +checking (be-sure to use the correct macro and ensure the array is +aligned and in correct byte-swap order in order to get useful +results). The following code shows how you might use these variables +and macros to code a loop in C that computes a simple 2-d weighted +averaging filter. + +.. code-block:: c++ + + int i,j; + for(i=1;i<Na[0]-1;i++) { + for(j=1;j<Na[1]-1;j++) { + B2(i,j) = A2(i,j) + (A2(i-1,j) + + A2(i+1,j)+A2(i,j-1) + + A2(i,j+1))*0.5 + + (A2(i-1,j-1) + + A2(i-1,j+1) + + A2(i+1,j-1) + + A2(i+1,j+1))*0.25 + } + } + +The above code doesn't have any error checking and so could fail with +a Python crash if, ``a`` had the wrong number of dimensions, or ``b`` +did not have the same shape as ``a``. However, it could be placed +inside a standard Python function with the necessary error checking to +produce a robust but fast subroutine. + +One final note about weave.inline: if you have additional code you +want to include in the final extension module such as supporting +function calls, include statments, etc. you can pass this code in as a +string using the keyword support_code: ``weave.inline(code, variables, +support_code=support)``. If you need the extension module to link +against an additional library then you can also pass in +distutils-style keyword arguments such as library_dirs, libraries, +and/or runtime_library_dirs which point to the appropriate libraries +and directories. + +Simplify creation of an extension module +---------------------------------------- + +The inline function creates one extension module for each function to- +be inlined. It also generates a lot of intermediate code that is +duplicated for each extension module. If you have several related +codes to execute in C, it would be better to make them all separate +functions in a single extension module with multiple functions. You +can also use the tools weave provides to produce this larger extension +module. In fact, the weave.inline function just uses these more +general tools to do its work. + +The approach is to: + +1. construct a extension module object using + ext_tools.ext_module(``module_name``); + +2. create function objects using ext_tools.ext_function(``func_name``, + ``code``, ``variables``); + +3. (optional) add support code to the function using the + .customize.add_support_code( ``support_code`` ) method of the + function object; + +4. add the functions to the extension module object using the + .add_function(``func``) method; + +5. when all the functions are added, compile the extension with its + .compile() method. + +Several examples are available in the examples directory where weave +is installed on your system. Look particularly at ramp2.py, +increment_example.py and fibonacii.py + + +Conclusion +---------- + +Weave is a useful tool for quickly routines in C/C++ and linking them +into Python. It's caching-mechanism allows for on-the-fly compilation +which makes it particularly attractive for in-house code. Because of +the requirement that the user have a C++-compiler, it can be difficult +(but not impossible) to distribute a package that uses weave to other +users who don't have a compiler installed. Of course, weave could be +used to construct an extension module which is then distributed in the +normal way *(* using a setup.py file). While you can use weave to +build larger extension modules with many methods, creating methods +with a variable- number of arguments is not possible. Thus, for a more +sophisticated module, you will still probably want a Python-layer that +calls the weave-produced extension. + +.. index:: + single: weave + + +Pyrex +===== + +Pyrex is a way to write C-extension modules using Python-like syntax. +It is an interesting way to generate extension modules that is growing +in popularity, particularly among people who have rusty or non- +existent C-skills. It does require the user to write the "interface" +code and so is more time-consuming than SWIG or f2py if you are trying +to interface to a large library of code. However, if you are writing +an extension module that will include quite a bit of your own +algorithmic code, as well, then Pyrex is a good match. A big weakness +perhaps is the inability to easily and quickly access the elements of +a multidimensional array. + +.. index:: + single: pyrex + +Notice that Pyrex is an extension-module generator only. Unlike weave +or f2py, it includes no automatic facility for compiling and linking +the extension module (which must be done in the usual fashion). It +does provide a modified distutils class called build_ext which lets +you build an extension module from a .pyx source. Thus, you could +write in a setup.py file: + +.. code-block:: python + + from Pyrex.Distutils import build_ext + from distutils.extension import Extension + from distutils.core import setup + + import numpy + py_ext = Extension('mine', ['mine.pyx'], + include_dirs=[numpy.get_include()]) + + setup(name='mine', description='Nothing', + ext_modules=[pyx_ext], + cmdclass = {'build_ext':build_ext}) + +Adding the NumPy include directory is, of course, only necessary if +you are using NumPy arrays in the extension module (which is what I +assume you are using Pyrex for). The distutils extensions in NumPy +also include support for automatically producing the extension-module +and linking it from a ``.pyx`` file. It works so that if the user does +not have Pyrex installed, then it looks for a file with the same +file-name but a ``.c`` extension which it then uses instead of trying +to produce the ``.c`` file again. + +Pyrex does not natively understand NumPy arrays. However, it is not +difficult to include information that lets Pyrex deal with them +usefully. In fact, the numpy.random.mtrand module was written using +Pyrex so an example of Pyrex usage is already included in the NumPy +source distribution. That experience led to the creation of a standard +c_numpy.pxd file that you can use to simplify interacting with NumPy +array objects in a Pyrex-written extension. The file may not be +complete (it wasn't at the time of this writing). If you have +additions you'd like to contribute, please send them. The file is +located in the .../site-packages/numpy/doc/pyrex directory where you +have Python installed. There is also an example in that directory of +using Pyrex to construct a simple extension module. It shows that +Pyrex looks a lot like Python but also contains some new syntax that +is necessary in order to get C-like speed. + +If you just use Pyrex to compile a standard Python module, then you +will get a C-extension module that runs either as fast or, possibly, +more slowly than the equivalent Python module. Speed increases are +possible only when you use cdef to statically define C variables and +use a special construct to create for loops: + +.. code-block:: none + + cdef int i + for i from start <= i < stop + +Let's look at two examples we've seen before to see how they might be +implemented using Pyrex. These examples were compiled into extension +modules using Pyrex-0.9.3.1. + + +Pyrex-add +--------- + +Here is part of a Pyrex-file I named add.pyx which implements the add +functions we previously implemented using f2py: + +.. code-block:: none + + cimport c_numpy + from c_numpy cimport import_array, ndarray, npy_intp, npy_cdouble, \ + npy_cfloat, NPY_DOUBLE, NPY_CDOUBLE, NPY_FLOAT, \ + NPY_CFLOAT + + #We need to initialize NumPy + import_array() + + def zadd(object ao, object bo): + cdef ndarray c, a, b + cdef npy_intp i + a = c_numpy.PyArray_ContiguousFromAny(ao, + NPY_CDOUBLE, 1, 1) + b = c_numpy.PyArray_ContiguousFromAny(bo, + NPY_CDOUBLE, 1, 1) + c = c_numpy.PyArray_SimpleNew(a.nd, a.dimensions, + a.descr.type_num) + for i from 0 <= i < a.dimensions[0]: + (<npy_cdouble *>c.data)[i].real = \ + (<npy_cdouble *>a.data)[i].real + \ + (<npy_cdouble *>b.data)[i].real + (<npy_cdouble *>c.data)[i].imag = \ + (<npy_cdouble *>a.data)[i].imag + \ + (<npy_cdouble *>b.data)[i].imag + return c + +This module shows use of the ``cimport`` statement to load the +definitions from the c_numpy.pxd file. As shown, both versions of the +import statement are supported. It also shows use of the NumPy C-API +to construct NumPy arrays from arbitrary input objects. The array c is +created using PyArray_SimpleNew. Then the c-array is filled by +addition. Casting to a particiular data-type is accomplished using +<cast \*>. Pointers are de-referenced with bracket notation and +members of structures are accessed using '.' notation even if the +object is techinically a pointer to a structure. The use of the +special for loop construct ensures that the underlying code will have +a similar C-loop so the addition calculation will proceed quickly. +Notice that we have not checked for NULL after calling to the C-API +--- a cardinal sin when writing C-code. For routines that return +Python objects, Pyrex inserts the checks for NULL into the C-code for +you and returns with failure if need be. There is also a way to get +Pyrex to automatically check for exceptions when you call functions +that don't return Python objects. See the documentation of Pyrex for +details. + + +Pyrex-filter +------------ + +The two-dimensional example we created using weave is a bit uglierto +implement in Pyrex because two-dimensional indexing using Pyrex is not +as simple. But, it is straightforward (and possibly faster because of +pre-computed indices). Here is the Pyrex-file I named image.pyx. + +.. code-block:: none + + cimport c_numpy + from c_numpy cimport import_array, ndarray, npy_intp,\ + NPY_DOUBLE, NPY_CDOUBLE, \ + NPY_FLOAT, NPY_CFLOAT, NPY_ALIGNED \ + + #We need to initialize NumPy + import_array() + def filter(object ao): + cdef ndarray a, b + cdef npy_intp i, j, M, N, oS + cdef npy_intp r,rm1,rp1,c,cm1,cp1 + cdef double value + # Require an ALIGNED array + # (but not necessarily contiguous) + # We will use strides to access the elements. + a = c_numpy.PyArray_FROMANY(ao, NPY_DOUBLE, \ + 2, 2, NPY_ALIGNED) + b = c_numpy.PyArray_SimpleNew(a.nd,a.dimensions, \ + a.descr.type_num) + M = a.dimensions[0] + N = a.dimensions[1] + S0 = a.strides[0] + S1 = a.strides[1] + for i from 1 <= i < M-1: + r = i*S0 + rm1 = r-S0 + rp1 = r+S0 + oS = i*N + for j from 1 <= j < N-1: + c = j*S1 + cm1 = c-S1 + cp1 = c+S1 + (<double *>b.data)[oS+j] = \ + (<double *>(a.data+r+c))[0] + \ + ((<double *>(a.data+rm1+c))[0] + \ + (<double *>(a.data+rp1+c))[0] + \ + (<double *>(a.data+r+cm1))[0] + \ + (<double *>(a.data+r+cp1))[0])*0.5 + \ + ((<double *>(a.data+rm1+cm1))[0] + \ + (<double *>(a.data+rp1+cm1))[0] + \ + (<double *>(a.data+rp1+cp1))[0] + \ + (<double *>(a.data+rm1+cp1))[0])*0.25 + return b + +This 2-d averaging filter runs quickly because the loop is in C and +the pointer computations are done only as needed. However, it is not +particularly easy to understand what is happening. A 2-d image, ``in`` +, can be filtered using this code very quickly using: + +.. code-block:: python + + import image + out = image.filter(in) + + +Conclusion +---------- + +There are several disadvantages of using Pyrex: + +1. The syntax for Pyrex can get a bit bulky, and it can be confusing at + first to understand what kind of objects you are getting and how to + interface them with C-like constructs. + +2. Inappropriate Pyrex syntax or incorrect calls to C-code or type- + mismatches can result in failures such as + + 1. Pyrex failing to generate the extension module source code, + + 2. Compiler failure while generating the extension module binary due to + incorrect C syntax, + + 3. Python failure when trying to use the module. + + +3. It is easy to lose a clean separation between Python and C which makes + re-using your C-code for other non-Python-related projects more + difficult. + +4. Multi-dimensional arrays are "bulky" to index (appropriate macros + may be able to fix this). + +5. The C-code generated by Prex is hard to read and modify (and typically + compiles with annoying but harmless warnings). + +Writing a good Pyrex extension module still takes a bit of effort +because not only does it require (a little) familiarity with C, but +also with Pyrex's brand of Python-mixed-with C. One big advantage of +Pyrex-generated extension modules is that they are easy to distribute +using distutils. In summary, Pyrex is a very capable tool for either +gluing C-code or generating an extension module quickly and should not +be over-looked. It is especially useful for people that can't or won't +write C-code or Fortran code. But, if you are already able to write +simple subroutines in C or Fortran, then I would use one of the other +approaches such as f2py (for Fortran), ctypes (for C shared- +libraries), or weave (for inline C-code). + +.. index:: + single: pyrex + + + + +ctypes +====== + +Ctypes is a python extension module (downloaded separately for Python +<2.5 and included with Python 2.5) that allows you to call an +arbitrary function in a shared library directly from Python. This +approach allows you to interface with C-code directly from Python. +This opens up an enormous number of libraries for use from Python. The +drawback, however, is that coding mistakes can lead to ugly program +crashes very easily (just as can happen in C) because there is little +type or bounds checking done on the parameters. This is especially +true when array data is passed in as a pointer to a raw memory +location. The responsibility is then on you that the subroutine will +not access memory outside the actual array area. But, if you don't +mind living a little dangerously ctypes can be an effective tool for +quickly taking advantage of a large shared library (or writing +extended functionality in your own shared library). + +.. index:: + single: ctypes + +Because the ctypes approach exposes a raw interface to the compiled +code it is not always tolerant of user mistakes. Robust use of the +ctypes module typically involves an additional layer of Python code in +order to check the data types and array bounds of objects passed to +the underlying subroutine. This additional layer of checking (not to +mention the conversion from ctypes objects to C-data-types that ctypes +itself performs), will make the interface slower than a hand-written +extension-module interface. However, this overhead should be neglible +if the C-routine being called is doing any significant amount of work. +If you are a great Python programmer with weak C-skills, ctypes is an +easy way to write a useful interface to a (shared) library of compiled +code. + +To use c-types you must + +1. Have a shared library. + +2. Load the shared library. + +3. Convert the python objects to ctypes-understood arguments. + +4. Call the function from the library with the ctypes arguments. + + +Having a shared library +----------------------- + +There are several requirements for a shared library that can be used +with c-types that are platform specific. This guide assumes you have +some familiarity with making a shared library on your system (or +simply have a shared library available to you). Items to remember are: + +- A shared library must be compiled in a special way ( *e.g.* using + the -shared flag with gcc). + +- On some platforms (*e.g.* Windows) , a shared library requires a + .def file that specifies the functions to be exported. For example a + mylib.def file might contain. + + :: + + LIBRARY mylib.dll + EXPORTS + cool_function1 + cool_function2 + + Alternatively, you may be able to use the storage-class specifier + __declspec(dllexport) in the C-definition of the function to avoid the + need for this .def file. + +There is no standard way in Python distutils to create a standard +shared library (an extension module is a "special" shared library +Python understands) in a cross-platform manner. Thus, a big +disadvantage of ctypes at the time of writing this book is that it is +difficult to distribute in a cross-platform manner a Python extension +that uses c-types and includes your own code which should be compiled +as a shared library on the users system. + + +Loading the shared library +-------------------------- + +A simple, but robust way to load the shared library is to get the +absolute path name and load it using the cdll object of ctypes.: + +.. code-block:: python + + lib = ctypes.cdll[<full_path_name>] + +However, on Windows accessing an attribute of the cdll method will +load the first DLL by that name found in the current directory or on +the PATH. Loading the absolute path name requires a little finesse for +cross-platform work since the extension of shared libraries varies. +There is a ``ctypes.util.find_library`` utility available that can +simplify the process of finding the library to load but it is not +foolproof. Complicating matters, different platforms have different +default extensions used by shared libraries (e.g. .dll -- Windows, .so +-- Linux, .dylib -- Mac OS X). This must also be taken into account if +you are using c-types to wrap code that needs to work on several +platforms. + +NumPy provides a convenience function called +:func:`ctypeslib.load_library` (name, path). This function takes the name +of the shared library (including any prefix like 'lib' but excluding +the extension) and a path where the shared library can be located. It +returns a ctypes library object or raises an OSError if the library +cannot be found or raises an ImportError if the ctypes module is not +available. (Windows users: the ctypes library object loaded using +:func:`load_library` is always loaded assuming cdecl calling convention. +See the ctypes documentation under ctypes.windll and/or ctypes.oledll +for ways to load libraries under other calling conventions). + +The functions in the shared library are available as attributes of the +ctypes library object (returned from :func:`ctypeslib.load_library`) or +as items using ``lib['func_name']`` syntax. The latter method for +retrieving a function name is particularly useful if the function name +contains characters that are not allowable in Python variable names. + + +Converting arguments +-------------------- + +Python ints/longs, strings, and unicode objects are automatically +converted as needed to equivalent c-types arguments The None object is +also converted automatically to a NULL pointer. All other Python +objects must be converted to ctypes-specific types. There are two ways +around this restriction that allow c-types to integrate with other +objects. + +1. Don't set the argtypes attribute of the function object and define an + :obj:`_as_parameter_` method for the object you want to pass in. The + :obj:`_as_parameter_` method must return a Python int which will be passed + directly to the function. + +2. Set the argtypes attribute to a list whose entries contain objects + with a classmethod named from_param that knows how to convert your + object to an object that ctypes can understand (an int/long, string, + unicode, or object with the :obj:`_as_parameter_` attribute). + +NumPy uses both methods with a preference for the second method +because it can be safer. The ctypes attribute of the ndarray returns +an object that has an _as_parameter\_ attribute which returns an +integer representing the address of the ndarray to which it is +associated. As a result, one can pass this ctypes attribute object +directly to a function expecting a pointer to the data in your +ndarray. The caller must be sure that the ndarray object is of the +correct type, shape, and has the correct flags set or risk nasty +crashes if the data-pointer to inappropriate arrays are passsed in. + +To implement the second method, NumPy provides the class-factory +function :func:`ndpointer` in the :mod:`ctypeslib` module. This +class-factory function produces an appropriate class that can be +placed in an argtypes attribute entry of a ctypes function. The class +will contain a from_param method which ctypes will use to convert any +ndarray passed in to the function to a ctypes-recognized object. In +the process, the conversion will perform checking on any properties of +the ndarray that were specified by the user in the call to :func:`ndpointer`. +Aspects of the ndarray that can be checked include the data-type, the +number-of-dimensions, the shape, and/or the state of the flags on any +array passed. The return value of the from_param method is the ctypes +attribute of the array which (because it contains the _as_parameter\_ +attribute pointing to the array data area) can be used by ctypes +directly. + +The ctypes attribute of an ndarray is also endowed with additional +attributes that may be convenient when passing additional information +about the array into a ctypes function. The attributes **data**, +**shape**, and **strides** can provide c-types compatible types +corresponding to the data-area, the shape, and the strides of the +array. The data attribute reutrns a ``c_void_p`` representing a +pointer to the data area. The shape and strides attributes each return +an array of ctypes integers (or None representing a NULL pointer, if a +0-d array). The base ctype of the array is a ctype integer of the same +size as a pointer on the platform. There are also methods +data_as({ctype}), shape_as(<base ctype>), and strides_as(<base +ctype>). These return the data as a ctype object of your choice and +the shape/strides arrays using an underlying base type of your choice. +For convenience, the **ctypeslib** module also contains **c_intp** as +a ctypes integer data-type whose size is the same as the size of +``c_void_p`` on the platform (it's value is None if ctypes is not +installed). + + +Calling the function +-------------------- + +The function is accessed as an attribute of or an item from the loaded +shared-library. Thus, if "./mylib.so" has a function named +"cool_function1" , I could access this function either as: + +.. code-block:: python + + lib = numpy.ctypeslib.load_library('mylib','.') + func1 = lib.cool_function1 # or equivalently + func1 = lib['cool_function1'] + +In ctypes, the return-value of a function is set to be 'int' by +default. This behavior can be changed by setting the restype attribute +of the function. Use None for the restype if the function has no +return value ('void'): + +.. code-block:: python + + func1.restype = None + +As previously discussed, you can also set the argtypes attribute of +the function in order to have ctypes check the types of the input +arguments when the function is called. Use the :func:`ndpointer` factory +function to generate a ready-made class for data-type, shape, and +flags checking on your new function. The :func:`ndpointer` function has the +signature + +.. function:: ndpointer(dtype=None, ndim=None, shape=None, flags=None) + + Keyword arguments with the value ``None`` are not checked. + Specifying a keyword enforces checking of that aspect of the + ndarray on conversion to a ctypes-compatible object. The dtype + keyword can be any object understood as a data-type object. The + ndim keyword should be an integer, and the shape keyword should be + an integer or a sequence of integers. The flags keyword specifies + the minimal flags that are required on any array passed in. This + can be specified as a string of comma separated requirements, an + integer indicating the requirement bits OR'd together, or a flags + object returned from the flags attribute of an array with the + necessary requirements. + +Using an ndpointer class in the argtypes method can make it +significantly safer to call a C-function using ctypes and the data- +area of an ndarray. You may still want to wrap the function in an +additional Python wrapper to make it user-friendly (hiding some +obvious arguments and making some arguments output arguments). In this +process, the **requires** function in NumPy may be useful to return the right kind of array from +a given input. + + +Complete example +---------------- + +In this example, I will show how the addition function and the filter +function implemented previously using the other approaches can be +implemented using ctypes. First, the C-code which implements the +algorithms contains the functions zadd, dadd, sadd, cadd, and +dfilter2d. The zadd function is: + +.. code-block:: c + + /* Add arrays of contiguous data */ + typedef struct {double real; double imag;} cdouble; + typedef struct {float real; float imag;} cfloat; + void zadd(cdouble *a, cdouble *b, cdouble *c, long n) + { + while (n--) { + c->real = a->real + b->real; + c->imag = a->imag + b->imag; + a++; b++; c++; + } + } + +with similar code for cadd, dadd, and sadd that handles complex float, +double, and float data-types, respectively: + +.. code-block:: c + + void cadd(cfloat *a, cfloat *b, cfloat *c, long n) + { + while (n--) { + c->real = a->real + b->real; + c->imag = a->imag + b->imag; + a++; b++; c++; + } + } + void dadd(double *a, double *b, double *c, long n) + { + while (n--) { + *c++ = *a++ + *b++; + } + } + void sadd(float *a, float *b, float *c, long n) + { + while (n--) { + *c++ = *a++ + *b++; + } + } + +The code.c file also contains the function dfilter2d: + +.. code-block:: c + + /* Assumes b is contiguous and + a has strides that are multiples of sizeof(double) + */ + void + dfilter2d(double *a, double *b, int *astrides, int *dims) + { + int i, j, M, N, S0, S1; + int r, c, rm1, rp1, cp1, cm1; + + M = dims[0]; N = dims[1]; + S0 = astrides[0]/sizeof(double); + S1=astrides[1]/sizeof(double); + for (i=1; i<M-1; i++) { + r = i*S0; rp1 = r+S0; rm1 = r-S0; + for (j=1; j<N-1; j++) { + c = j*S1; cp1 = j+S1; cm1 = j-S1; + b[i*N+j] = a[r+c] + \ + (a[rp1+c] + a[rm1+c] + \ + a[r+cp1] + a[r+cm1])*0.5 + \ + (a[rp1+cp1] + a[rp1+cm1] + \ + a[rm1+cp1] + a[rm1+cp1])*0.25; + } + } + } + +A possible advantage this code has over the Fortran-equivalent code is +that it takes arbitrarily strided (i.e. non-contiguous arrays) and may +also run faster depending on the optimization capability of your +compiler. But, it is a obviously more complicated than the simple code +in filter.f. This code must be compiled into a shared library. On my +Linux system this is accomplished using:: + + gcc -o code.so -shared code.c + +Which creates a shared_library named code.so in the current directory. +On Windows don't forget to either add __declspec(dllexport) in front +of void on the line preceeding each function definition, or write a +code.def file that lists the names of the functions to be exported. + +A suitable Python interface to this shared library should be +constructed. To do this create a file named interface.py with the +following lines at the top: + +.. code-block:: python + + __all__ = ['add', 'filter2d'] + + import numpy as N + import os + + _path = os.path.dirname('__file__') + lib = N.ctypeslib.load_library('code', _path) + _typedict = {'zadd' : complex, 'sadd' : N.single, + 'cadd' : N.csingle, 'dadd' : float} + for name in _typedict.keys(): + val = getattr(lib, name) + val.restype = None + _type = _typedict[name] + val.argtypes = [N.ctypeslib.ndpointer(_type, + flags='aligned, contiguous'), + N.ctypeslib.ndpointer(_type, + flags='aligned, contiguous'), + N.ctypeslib.ndpointer(_type, + flags='aligned, contiguous,'\ + 'writeable'), + N.ctypeslib.c_intp] + +This code loads the shared library named code.{ext} located in the +same path as this file. It then adds a return type of void to the +functions contained in the library. It also adds argument checking to +the functions in the library so that ndarrays can be passed as the +first three arguments along with an integer (large enough to hold a +pointer on the platform) as the fourth argument. + +Setting up the filtering function is similar and allows the filtering +function to be called with ndarray arguments as the first two +arguments and with pointers to integers (large enough to handle the +strides and shape of an ndarray) as the last two arguments.: + +.. code-block:: python + + lib.dfilter2d.restype=None + lib.dfilter2d.argtypes = [N.ctypeslib.ndpointer(float, ndim=2, + flags='aligned'), + N.ctypeslib.ndpointer(float, ndim=2, + flags='aligned, contiguous,'\ + 'writeable'), + ctypes.POINTER(N.ctypeslib.c_intp), + ctypes.POINTER(N.ctypeslib.c_intp)] + +Next, define a simple selection function that chooses which addition +function to call in the shared library based on the data-type: + +.. code-block:: python + + def select(dtype): + if dtype.char in ['?bBhHf']: + return lib.sadd, single + elif dtype.char in ['F']: + return lib.cadd, csingle + elif dtype.char in ['DG']: + return lib.zadd, complex + else: + return lib.dadd, float + return func, ntype + +Finally, the two functions to be exported by the interface can be +written simply as: + +.. code-block:: python + + def add(a, b): + requires = ['CONTIGUOUS', 'ALIGNED'] + a = N.asanyarray(a) + func, dtype = select(a.dtype) + a = N.require(a, dtype, requires) + b = N.require(b, dtype, requires) + c = N.empty_like(a) + func(a,b,c,a.size) + return c + +and: + +.. code-block:: python + + def filter2d(a): + a = N.require(a, float, ['ALIGNED']) + b = N.zeros_like(a) + lib.dfilter2d(a, b, a.ctypes.strides, a.ctypes.shape) + return b + + +Conclusion +---------- + +.. index:: + single: ctypes + +Using ctypes is a powerful way to connect Python with arbitrary +C-code. It's advantages for extending Python include + +- clean separation of C-code from Python code + + - no need to learn a new syntax except Python and C + + - allows re-use of C-code + + - functionality in shared libraries written for other purposes can be + obtained with a simple Python wrapper and search for the library. + + +- easy integration with NumPy through the ctypes attribute + +- full argument checking with the ndpointer class factory + +It's disadvantages include + +- It is difficult to distribute an extension module made using ctypes + because of a lack of support for building shared libraries in + distutils (but I suspect this will change in time). + +- You must have shared-libraries of your code (no static libraries). + +- Very little support for C++ code and it's different library-calling + conventions. You will probably need a C-wrapper around C++ code to use + with ctypes (or just use Boost.Python instead). + +Because of the difficulty in distributing an extension module made +using ctypes, f2py is still the easiest way to extend Python for +package creation. However, ctypes is a close second and will probably +be growing in popularity now that it is part of the Python +distribution. This should bring more features to ctypes that should +eliminate the difficulty in extending Python and distributing the +extension using ctypes. + + +Additional tools you may find useful +==================================== + +These tools have been found useful by others using Python and so are +included here. They are discussed separately because I see them as +either older ways to do things more modernly handled by f2py, weave, +Pyrex, or ctypes (SWIG, PyFort, PyInline) or because I don't know much +about them (SIP, Boost, Instant). I have not added links to these +methods because my experience is that you can find the most relevant +link faster using Google or some other search engine, and any links +provided here would be quickly dated. Do not assume that just because +it is included in this list, I don't think the package deserves your +attention. I'm including information about these packages because many +people have found them useful and I'd like to give you as many options +as possible for tackling the problem of easily integrating your code. + + +SWIG +---- + +.. index:: + single: swig + +Simplified Wrapper and Interface Generator (SWIG) is an old and fairly +stable method for wrapping C/C++-libraries to a large variety of other +languages. It does not specifically understand NumPy arrays but can be +made useable with NumPy through the use of typemaps. There are some +sample typemaps in the numpy/doc/swig directory under numpy.i along +with an example module that makes use of them. SWIG excels at wrapping +large C/C++ libraries because it can (almost) parse their headers and +auto-produce an interface. Technically, you need to generate a ``.i`` +file that defines the interface. Often, however, this ``.i`` file can +be parts of the header itself. The interface usually needs a bit of +tweaking to be very useful. This ability to parse C/C++ headers and +auto-generate the interface still makes SWIG a useful approach to +adding functionalilty from C/C++ into Python, despite the other +methods that have emerged that are more targeted to Python. SWIG can +actually target extensions for several languages, but the typemaps +usually have to be language-specific. Nonetheless, with modifications +to the Python-specific typemaps, SWIG can be used to interface a +library with other languages such as Perl, Tcl, and Ruby. + +My experience with SWIG has been generally positive in that it is +relatively easy to use and quite powerful. I used to use it quite +often before becoming more proficient at writing C-extensions. +However, I struggled writing custom interfaces with SWIG because it +must be done using the concept of typemaps which are not Python +specific and are written in a C-like syntax. Therefore, I tend to +prefer other gluing strategies and would only attempt to use SWIG to +wrap a very-large C/C++ library. Nonetheless, there are others who use +SWIG quite happily. + + +SIP +--- + +.. index:: + single: SIP + +SIP is another tool for wrapping C/C++ libraries that is Python +specific and appears to have very good support for C++. Riverbank +Computing developed SIP in order to create Python bindings to the QT +library. An interface file must be written to generate the binding, +but the interface file looks a lot like a C/C++ header file. While SIP +is not a full C++ parser, it understands quite a bit of C++ syntax as +well as its own special directives that allow modification of how the +Python binding is accomplished. It also allows the user to define +mappings between Python types and C/C++ structrues and classes. + + +Boost Python +------------ + +.. index:: + single: Boost.Python + +Boost is a repository of C++ libraries and Boost.Python is one of +those libraries which provides a concise interface for binding C++ +classes and functions to Python. The amazing part of the Boost.Python +approach is that it works entirely in pure C++ without introducing a +new syntax. Many users of C++ report that Boost.Python makes it +possible to combine the best of both worlds in a seamless fashion. I +have not used Boost.Python because I am not a big user of C++ and +using Boost to wrap simple C-subroutines is usually over-kill. It's +primary purpose is to make C++ classes available in Python. So, if you +have a set of C++ classes that need to be integrated cleanly into +Python, consider learning about and using Boost.Python. + + +Instant +------- + +.. index:: + single: Instant + +This is a relatively new package (called pyinstant at sourceforge) +that builds on top of SWIG to make it easy to inline C and C++ code in +Python very much like weave. However, Instant builds extension modules +on the fly with specific module names and specific method names. In +this repsect it is more more like f2py in its behavior. The extension +modules are built on-the fly (as long as the SWIG is installed). They +can then be imported. Here is an example of using Instant with NumPy +arrays (adapted from the test2 included in the Instant distribution): + +.. code-block:: python + + code=""" + PyObject* add(PyObject* a_, PyObject* b_){ + /* + various checks + */ + PyArrayObject* a=(PyArrayObject*) a_; + PyArrayObject* b=(PyArrayObject*) b_; + int n = a->dimensions[0]; + int dims[1]; + dims[0] = n; + PyArrayObject* ret; + ret = (PyArrayObject*) PyArray_FromDims(1, dims, NPY_DOUBLE); + int i; + char *aj=a->data; + char *bj=b->data; + double *retj = (double *)ret->data; + for (i=0; i < n; i++) { + *retj++ = *((double *)aj) + *((double *)bj); + aj += a->strides[0]; + bj += b->strides[0]; + } + return (PyObject *)ret; + } + """ + import Instant, numpy + ext = Instant.Instant() + ext.create_extension(code=s, headers=["numpy/arrayobject.h"], + include_dirs=[numpy.get_include()], + init_code='import_array();', module="test2b_ext") + import test2b_ext + a = numpy.arange(1000) + b = numpy.arange(1000) + d = test2b_ext.add(a,b) + +Except perhaps for the dependence on SWIG, Instant is a +straightforward utility for writing extension modules. + + +PyInline +-------- + +This is a much older module that allows automatic building of +extension modules so that C-code can be included with Python code. +It's latest release (version 0.03) was in 2001, and it appears that it +is not being updated. + + +PyFort +------ + +PyFort is a nice tool for wrapping Fortran and Fortran-like C-code +into Python with support for Numeric arrays. It was written by Paul +Dubois, a distinguished computer scientist and the very first +maintainer of Numeric (now retired). It is worth mentioning in the +hopes that somebody will update PyFort to work with NumPy arrays as +well which now support either Fortran or C-style contiguous arrays. |