2010-06-15 12 views
41

J'ai un certain nombre de fonctions 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 la façon dont cela est fait. Ma fonction C ressemble à ceci:.Enveloppement simple du code C avec Cython

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 préciser les trois premiers paramètres (une chaîne et deux entiers), et récupérer 8 tableaux de numpy (ou listes de python Toutes les doubles tableaux ont N éléments). Mon code suppose que les pointeurs pointent vers un morceau de mémoire déjà alloué. En outre, le code C produit doit être lié à certaines bibliothèques externes.

+0

Je viens Enveloppé ma bibliothèque C en utilisant Cython, vous voudrez peut-être prendre Regardez cela pour un exemple sur la façon de le faire. J'ai expliqué tout le processus en détail ici, y compris la construction et la distribution du module: http://martinsosic.com/development/2016/02/08/wrapping-c-library-as-python-module.html. – Martinsos

Répondre

63

Voici un petit exemple, mais complète de tableaux de passage numpy à une fonction externe C, logiquement

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

utilisant Cython. (Ceci est certainement bien connu pour ceux qui le connaissent bien commentaires sont les bienvenus Dernière modification:... 23 février 2011, pour Cython 0,14)

Première lecture ou écrémé Cython build et Cython with NumPy.

deux étapes:

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

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

Pour les étudiants, je suggère: faire un diagramme de ces étapes, regarder à travers les fichiers ci-dessous, puis téléchargez et exécutez-les.

(distutils est un énorme paquet, alambiqué utilisé pour fabriquons des emballages en Python pour la distribution, et les installer. Ici nous utilisons juste une petite partie de compiler et lien vers f.so. Cette étape n'a rien à voir avec Cython, mais il peut être source de confusion,.. simples erreurs dans un .pyx peut provoquer des pages de messages d'erreur obscurs de g ++ compiler et lier Voir aussi distutils doc et/ou SO questions on distutils)

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

Une alternative à setup.py serait d'exécuter les étapes séparément, dans un script ou 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 le cc-lib-mac emballage ci-dessous pour votre plate-forme et l'installation: ce n'est pas joli, mais petit.

Pour de vrais exemples de C Cython C, regarder les fichiers .pyx dans à peu près tous SciKit. Voir également: Cython for NumPy users et SO questions/tagged/cython.


Pour décompresser les fichiers suivants, par copier-coller le lot à un gros fichier, dire cython-numpy-c-demo, puis sous Unix (dans un nouveau répertoire propre) exécuter 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 ... 
# https://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 \ 
    "[email protected]" \ 
    -o $base.so 

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

# 23 Feb 2011 13:38 
+1

Il n'est vraiment pas nécessaire d'utiliser une fonction wrapper qui prend un pointeur 'char *'. Vous pouvez envelopper 'fcreal()' directement dans Cython et l'appeler comme 'fcret = fcreal (N, A.data, B.data, Z.data)'. En outre, il est sujet à erreur et non portable pour compiler 'fc.o' séparément. Incluez juste 'fc.cpp' dans' sources = '. – oceanhug

+1

Cela produira probablement des résultats inattendus si le tableau numpy passé n'est pas continu dans la mémoire ou a l'ordre des octets Fortran. En outre, le casting requis est un peu méchant. Voir ci-dessous pour un meilleur code de Cython. – Nikratio

+0

@denis J'ai un [post Cython] (http://stackoverflow.com/questions/41944883/verifying-compatibility-in-compiling-extension-types-and-using-them-with-cdef) vous pourrez peut-être fournir un aperçu sur. – Phillip

2

Vous devriez vérifier Ctypes c'est probablement la chose la plus facile à utiliser si tout ce que vous voulez est une fonction.

+0

Vrai, mais je voudrais envelopper d'autres choses en utilisant Cython plus tard, donc c'est mon point de départ :) – Jose

+3

L'utilisation de ctypes même pour les petits wrappers est dangereuse et fragile en raison des failles de cette approche générale (n'utilisant pas les fichiers d'en-tête). avant). –

+0

La question porte sur Cython; cette question ne répond à rien. –

3

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

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

passe alors dans le pointeur .data de chacun à votre fonction C. Cela devrait fonctionner. Si vous n'avez pas besoin de commencer par des zéros, vous pouvez utiliser np.empty pour un petit boost de vitesse.

Voir le didacticiel Cython for NumPy Users dans les documents (l'a corrigé pour le lien correct).

12

Le code Cython suivant de http://article.gmane.org/gmane.comp.python.cython.user/5625 ne nécessite pas des moulages explicites et gère également des réseaux non continous:

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]) 
+0

Est-ce que cela entraîne une copie supplémentaire de la mémoire (si le tableau est déjà contigu)? – dashesy

+0

@dashesy: ​​non, si le tableau est déjà contigu, il n'y a pas de copie supplémentaire. – Nikratio

+0

Si A_c est 1-D, est-ce correct de passer comme fc (& A_c [0])? – ascetic652