43 votes

Emballage simple de code C avec Cython

J'ai un certain nombre de fonctions en C, et je voudrais les appeler à partir de python. cython semble être la voie à suivre, mais je ne peux pas vraiment trouver un exemple de comment cela est fait. Ma fonction C ressemble à ceci:

void calculate_daily ( char *db_name, int grid_id, int year,
                       double *dtmp, double *dtmn, double *dtmx, 
                       double *dprec, double *ddtr, double *dayl, 
                       double *dpet, double *dpar ) ;

Tout ce que je veux faire est de spécifier les trois premiers paramètres (une chaîne et deux entiers), et de récupérer les 8 tableaux numpy (ou python listes. Tous les doubles des tableaux de N éléments). Mon code suppose que les pointeurs pointent vers un déjà alloué partie de la mémoire. Aussi, le produit du code C doit lier à des bibliothèques externes.

64voto

denis Points 7316

Voici un petit mais complet exemple de passer des tableaux numpy externe de fonction C, logiquement

fc( int N, double* a, double* b, double* z )  # z = a + b

à l'aide de Cython. (C'est sûrement bien connu de ceux qui le connaissent bien. Les commentaires sont les bienvenus. Dernière modification: 23 Fév 2011, pour Cython 0.14.)

D'abord lire ou écrémé Cython construire et Cython avec NumPy .

2 étapes:

  • python f-setup.py build_ext --inplace
    virages f.pyx et fc.cpp -> f.so, une bibliothèque dynamique
  • python test-f.py
    import f charge f.so; f.fpy( ... ) appelle la C fc( ... ).

python f-setup.py utilise distutils pour exécuter cython, compiler et lier:
cython f.pyx -> f.cpp
compiler f.cpp et fc.cpp
lien f.o fc.o -> f.so, une dynamique lib que python import f à charger.

Pour les étudiants, je vous suggère de: faire un schéma de ces étapes, regardez dans les fichiers ci-dessous, puis de les télécharger et de les exécuter.

(distutils est un énorme alambiquée logiciel utilisé pour la faire les paquets Python pour la distribution, et de les installer. Ici, nous sommes en utilisant seulement une petite partie de compiler et lier de f.so. Cette étape n'a rien à voir avec Cython, mais il peut être source de confusion; les erreurs simples dans un .custode peut causer des pages d'un obscur des messages d'erreur à partir de g++ compiler et lier. Voir aussi distutils doc et/ou DONC des questions sur distutils .)

Comme make, setup.py reprise cython f.pyx et g++ -c ... f.cpp si f.pyx est plus récente que f.cpp.
Pour le nettoyage, rm -r build/ .

Une alternative à l' setup.py serait d'exécuter les étapes séparément, dans un script ou un fichier Makefile:
cython --cplus f.pyx -> f.cpp # see cython -h
g++ -c ... f.cpp -> f.o
g++ -c ... fc.cpp -> fc.o
cc-lib f.o fc.o -> dynamic library f.so.
Modifier l' cc-lib-macwrapper ci-dessous pour votre plate-forme et de l'installation: c'est pas joli, mais petite.

Pour des exemples concrets de Cython emballage C, regarder .custode fichiers dans n'importe quel SciKit .

Voir aussi: Cython pour NumPy utilisateurs et DONC, questions/tagged/cython .


Pour décompresser les fichiers suivants, coupez-collez le beaucoup à un gros fichier, disons cython-numpy-c-demo, puis dans Unix (dans le propre répertoire) exécutez sh cython-numpy-c-demo.

#--------------------------------------------------------------------------------
cat >f.pyx <<\!
# f.pyx: numpy arrays -> extern from "fc.h"
# 3 steps:
# cython f.pyx  -> f.c
# link: python f-setup.py build_ext --inplace  -> f.so, a dynamic library
# py test-f.py: import f gets f.so, f.fpy below calls fc()

import numpy as np
cimport numpy as np

cdef extern from "fc.h": 
    int fc( int N, double* a, double* b, double* z )  # z = a + b

def fpy( N,
    np.ndarray[np.double_t,ndim=1] A,
    np.ndarray[np.double_t,ndim=1] B,
    np.ndarray[np.double_t,ndim=1] Z ):
    """ wrap np arrays to fc( a.data ... ) """
    assert N <= len(A) == len(B) == len(Z)
    fcret = fc( N, <double*> A.data, <double*> B.data, <double*> Z.data )
        # fcret = fc( N, A.data, B.data, Z.data )  grr char*
    return fcret

!

#--------------------------------------------------------------------------------
cat >fc.h <<\!
// fc.h: numpy arrays from cython , double*

int fc( int N, const double a[], const double b[], double z[] );
!

#--------------------------------------------------------------------------------
cat >fc.cpp <<\!
// fc.cpp: z = a + b, numpy arrays from cython

#include "fc.h"
#include <stdio.h>

int fc( int N, const double a[], const double b[], double z[] )
{
    printf( "fc: N=%d a[0]=%f b[0]=%f \n", N, a[0], b[0] );
    for( int j = 0;  j < N;  j ++ ){
        z[j] = a[j] + b[j];
    }
    return N;
}
!

#--------------------------------------------------------------------------------
cat >f-setup.py <<\!
# python f-setup.py build_ext --inplace
#   cython f.pyx -> f.cpp
#   g++ -c f.cpp -> f.o
#   g++ -c fc.cpp -> fc.o
#   link f.o fc.o -> f.so

# distutils uses the Makefile distutils.sysconfig.get_makefile_filename()
# for compiling and linking: a sea of options.

# http://docs.python.org/distutils/introduction.html
# http://docs.python.org/distutils/apiref.html  20 pages ...
# http://stackoverflow.com/questions/tagged/distutils+python

import numpy
from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext
# from Cython.Build import cythonize

ext_modules = [Extension(
    name="f",
    sources=["f.pyx", "fc.cpp"],
        # extra_objects=["fc.o"],  # if you compile fc.cpp separately
    include_dirs = [numpy.get_include()],  # .../site-packages/numpy/core/include
    language="c++",
        # libraries=
        # extra_compile_args = "...".split(),
        # extra_link_args = "...".split()
    )]

setup(
    name = 'f',
    cmdclass = {'build_ext': build_ext},
    ext_modules = ext_modules,
        # ext_modules = cythonize(ext_modules)  ? not in 0.14.1
    # version=
    # description=
    # author=
    # author_email=
    )

# test: import f
!

#--------------------------------------------------------------------------------
cat >test-f.py <<\!
#!/usr/bin/env python
# test-f.py

import numpy as np
import f  # loads f.so from cc-lib: f.pyx -> f.c + fc.o -> f.so

N = 3
a = np.arange( N, dtype=np.float64 )
b = np.arange( N, dtype=np.float64 )
z = np.ones( N, dtype=np.float64 ) * np.NaN

fret = f.fpy( N, a, b, z )
print "fpy -> fc z:", z

!

#--------------------------------------------------------------------------------
cat >cc-lib-mac <<\!
#!/bin/sh
me=${0##*/}
case $1 in
"" )
    set --  f.cpp fc.cpp ;;  # default: g++ these
-h* | --h* )
    echo "
$me [g++ flags] xx.c yy.cpp zz.o ...
    compiles .c .cpp .o files to a dynamic lib xx.so
"
    exit 1
esac

# Logically this is simple, compile and link,
# but platform-dependent, layers upon layers, gloom, doom

base=${1%.c*}
base=${base%.o}
set -x

g++ -dynamic -arch ppc \
    -bundle -undefined dynamic_lookup \
    -fno-strict-aliasing -fPIC -fno-common -DNDEBUG `# -g` -fwrapv \
    -isysroot /Developer/SDKs/MacOSX10.4u.sdk \
    -I/Library/Frameworks/Python.framework/Versions/2.6/include/python2.6 \
    -I${Pysite?}/numpy/core/include \
    -O2 -Wall \
    "$@" \
    -o $base.so

# undefs: nm -gpv $base.so | egrep '^ *U _+[^P]'
!

# 23 Feb 2011 13:38

12voto

Nikratio Points 697

Le code Cython suivant, extrait de http://article.gmane.org/gmane.comp.python.cython.user/5625, ne nécessite pas de conversions explicites et gère également les tableaux non continus:

 def fpy(A):
    cdef np.ndarray[np.double_t, ndim=2, mode="c"] A_c
    A_c = np.ascontiguousarray(A, dtype=np.double)
    fc(&A_c[0,0])
 

3voto

dwf Points 2011

Fondamentalement, vous pouvez écrire votre Cython fonction telle qu'elle alloue les tableaux (assurez-vous d' cimport numpy as np):

cdef np.ndarray[np.double_t, ndim=1] rr = np.zeros((N,), dtype=np.double)

puis la passer dans l' .data pointeur de fonction C. Cela devrait fonctionner. Si vous n'avez pas besoin de commencer avec des zéros vous pouvez utiliser np.empty pour un petit boost de vitesse.

Voir la Cython pour NumPy Utilisateurs tutoriel dans les docs (fixe pour le lien correct).

2voto

Yon Points 804

Vous devriez vérifier Ctypes . C’est probablement la chose la plus facile à utiliser si vous ne voulez qu’une fonction.

1voto

Stefan Behnel Points 11

Pensez à vous abonner à la liste des utilisateurs de cython sur Google-Groups. C'est le meilleur endroit pour poser ce genre de question.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X