2010-06-15 17 views
41

Ich habe eine Reihe von C-Funktionen, und ich möchte sie von Python aufrufen. Cython scheint der richtige Weg zu sein, aber ich kann nicht wirklich ein Beispiel dafür finden, wie genau das gemacht wird. Meine C-Funktion sieht wie folgt aus:.Einfache Umhüllung von C-Code mit 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) ; 

Alles, was ich tun möchte, ist die ersten drei Parameter angeben (einen String und zwei ganzen Zahlen), und erholen 8 numpy Arrays (oder Python-Listen Alle Doppel Arrays N Elemente). Mein Code geht davon aus, dass die Zeiger auf einen bereits zugewiesenen Speicherbereich zeigen. Außerdem sollte der erzeugte C-Code mit einigen externen Bibliotheken verknüpft sein.

+0

ich meine C-Bibliothek nur wrapped kürzlich mit Cython, können Sie eine nehmen wollen Schauen Sie sich das an, um ein Beispiel dafür zu bekommen. Ich erklärte den gesamten Prozess im Detail, einschließlich der Erstellung und Verteilung des Moduls: http://martinsosic.com/development/2016/02/08/wrapping-c-library-as-python-module.html. – Martinsos

Antwort

63

Hier ist ein kleines, aber vollständiges Beispiel vorbei numpy Arrays an eine externe C-Funktion, logisch

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

Cython verwenden. (Dies ist sicherlich gut bekannt für diejenigen, die es gut kennen Kommentare sind willkommen Letzte Änderung:... 23. Februar 2011, für Cython 0,14)

Erste Cython build und Cython with NumPy lesen oder abschöpfen.

2 Schritten:

  • python f-setup.py build_ext --inplace
    dreht f.pyx und fc.cpp ->f.so, eine dynamische Bibliothek
  • python test-f.py
    import f Lasten f.so; f.fpy(...) ruft die C fc(...) auf.

python f-setup.py verwendet distutils cython auszuführen, kompilieren und verknüpfen:
cython f.pyx -> f.cpp
kompilieren f.cpp und fc.cpp
Link f.o fc.o ->f.so, eine dynamische lib diesem Python import f geladen werden.

Für Studenten würde ich vorschlagen: Machen Sie ein Diagramm dieser Schritte, sehen Sie sich durch die Dateien unten, laden Sie sie herunter und führen Sie sie aus.

(distutils ist ein riesiges, gewundenes Paket verwendet, um Make Python-Pakete für die Verteilung, und sie installiert. Hier sind wir nur einen kleinen Teil davon mit kompilieren und Link zu f.so. Dieser Schritt hat nichts zu sie mit Cython, aber es kann verwirrend sein;.. in einem .pyx können dazu führen, Seiten unbekannter Fehlermeldungen von g ++ kompiliert und Link Siehe auch distutils doc und/oder SO questions on distutils)

Wie make, einfache Fehler setup.py wird cython f.pyx und g++ -c ... f.cpp erneut ausgeführt, wenn f.pyx neuer ist als f.cpp.
Aufzuräumen, rm -r build/.

Eine Alternative zu setup.py wäre die Schritte separat ausgeführt werden, in einem Skript oder 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.
Ändern Sie die cc-lib-mac Wrapper unten für Ihre Plattform und Installation: es ist nicht schön, aber klein.

Für echte Beispiele von Cython-Verpackung C, betrachten Sie .pyx-Dateien in etwa SciKit.

Siehe auch: Cython for NumPy users und SO questions/tagged/cython.


die folgenden Dateien entpacken, cut-paste die Menge zu einer großen Datei, sagt cython-numpy-c-demo, dann in Unix (in einem sauberen neuen Verzeichnis) sh cython-numpy-c-demo laufen.

#-------------------------------------------------------------------------------- 
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

Es ist wirklich nicht notwendig, eine Wrapper-Funktion zu verwenden, die einen 'char *' -Zeiger verwendet. Sie können 'fcreal()' direkt in Cython umbrechen und es als 'fcret = fcreal (N, A.data, B.data, Z.data) 'aufrufen. Außerdem ist es fehleranfällig und nicht portabel, 'fc.o' separat zu kompilieren. Fügen Sie einfach 'fc.cpp' in' sources = 'ein. – oceanhug

+1

Dies wird wahrscheinlich zu unerwarteten Ergebnissen führen, wenn das übergebene numpy Array nicht fortlaufend im Speicher ist oder eine Fortran-Byte-Reihenfolge aufweist. Außerdem ist die erforderliche Besetzung ein bisschen unangenehm. Siehe unten für besseren Cython-Code. – Nikratio

+0

@denis Ich habe eine [Cython Post] (http://stackoverflow.com/questions/41944883/verifying-compatibility-in-compiling-extension-types-and-use-them-with-cdef) können Sie möglicherweise Einblicke gewähren. – Phillip

2

Sie sollten Ctypes überprüfen, es ist wahrscheinlich die einfachste Sache zu verwenden, wenn alles, was Sie wollen, eine Funktion ist.

+0

Richtig, aber ich würde gerne andere Sachen später mit Cython umhüllen, also das ist mein Ausgangspunkt :) – Jose

+3

Die Verwendung von Ctypes sogar für kleine Wrapper ist gefährlich und zerbrechlich wegen der Mängel dieses allgemeinen Ansatzes (ohne Headerdateien und so weiter). –

+0

Die Frage fragt nach Cython; Diese Frage kann das nicht beantworten. –

3

Grundsätzlich können Sie Ihre Cython Funktion so schreiben, dass es die Arrays zuordnet (stellen Sie sicher, cimport numpy as np):

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

dann im .data Zeiger übergeben jeder auf Ihre C-Funktion. Das sollte funktionieren. Wenn Sie nicht mit Nullen beginnen müssen, können Sie für eine kleine Geschwindigkeitsverstärkung verwenden.

Siehe Tutorial in den Dokumenten (behoben, um den richtigen Link).

12

Die folgende Cython Code aus http://article.gmane.org/gmane.comp.python.cython.user/5625 keine explizite Casts erfordern und auch nicht-kontinuierliche Arrays behandelt:

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

Führt dies zu einer zusätzlichen Speicherkopie (wenn das Array bereits zusammenhängend ist)? – dashesy

+0

@dashesy: ​​Nein, wenn das Array bereits zusammenhängend ist, gibt es keine zusätzliche Kopie. – Nikratio

+0

Wenn A_c 1-D ist, ist es richtig, als fc (& A_c [0]) zu übergeben? – ascetic652