Proste owijanie kodu C za pomocą cythona

Mam wiele funkcji C i chciałbym je wywołać z Pythona. cython wydaje się być drogą do zrobienia, ale naprawdę nie mogę znaleźć przykładu, jak dokładnie to się robi. Moja funkcja C wygląda tak:

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 ) ;

Wszystko, co chcę zrobić, to określić pierwsze trzy parametry (ciąg znaków i dwie liczby całkowite) i odzyskać 8 tablic numpy (lub list Pythona. Wszystkie tablice podwójne mają N elementów). Mój kod zakłada, że wskaźniki wskazują na już przydzieloną część pamięci. Również, wytworzony kod C powinien zawierać linki do niektórych zewnętrznych bibliotek.

Author: Jose, 2010-06-15

4 answers

Oto mały, ale kompletny przykład przekazywania tablic numpy do zewnętrznej funkcji C, logicznie

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

Używając Cythona. (Jest to z pewnością dobrze znane tym, którzy dobrze to znają. Komentarze są mile widziane. Ostatnia zmiana: 23 lut 2011, Dla Cython 0.14.)

Najpierw Przeczytaj lub przejrzyj Cython build i Cython z NumPy .

2 kroki:

  • python f-setup.py build_ext --inplace
    turns f.pyx and fc.cpp -> f.so, biblioteka dynamiczna]}
  • python test-f.py
    import f obciążenia f.so; f.fpy( ... ) nazywa C fc( ... ).

python f-setup.py używa distutils do uruchamiania cythona, kompilacji i linkowania:
cython f.pyx -> f.cpp
compile f.cpp i fc.cpp
link f.o fc.o -> f.so, dynamiczny lib, który zostanie załadowany w Pythonie import f.

Dla uczniów, proponuję: zrobić schemat tych kroków, przejrzyj poniższe pliki, a następnie pobierz je i uruchom.

(distutils jest ogromnym, zawiłym pakietem używanym do dodać pakiety Pythona do dystrybucji i zainstalować je. Tutaj wykorzystujemy tylko niewielką jego część aby skompilować i link do f.so. Ten krok nie ma nic wspólnego z Cythonem, ale może być mylący; proste błędy w a .pyx może powodować strony niejasnych komunikatów o błędach z G++ compile i link. distutils doc i / lub więc pytania na temat distutils .)

Jak make, setup.py will rerun cython f.pyx i g++ -c ... f.cpp jeśli f.pyx jest nowsza niż f.cpp.
Do sprzątania, rm -r build/.

Alternatywą dla setup.py byłoby uruchomienie kroków osobno, w skrypcie lub 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.
Modify the cc-lib-mac wrapper poniżej dla Twojej platformy i instalacji: nie jest ładna, ale mała.

Dla prawdziwych przykładów owijania Cython C, spójrz .pliki pyx w prawie każdym SciKit .

Zobacz: Cython dla użytkowników NumPy and SO questions/tagged / cython .


Aby rozpakować następujące pliki, Wytnij-Wklej wiele do jednego dużego pliku, powiedzmy cython-numpy-c-demo, następnie w Unix (w czystym nowy katalog) Uruchom 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 \
    "$@" \
    -o $base.so

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

# 23 Feb 2011 13:38
 64
Author: denis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:47:18

Następujący kod Cythona z http://article.gmane.org/gmane.comp.python.cython.user/5625 nie wymaga jawnych rzutów, a także obsługuje tablice 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])
 12
Author: Nikratio,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-02-02 17:14:48

Zasadniczo możesz napisać swoją funkcję Cython w taki sposób, że przydziela ona tablice (upewnij się, że cimport numpy as np):

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

Następnie przekaż wskaźnik .data każdego z nich do funkcji C. To powinno zadziałać. Jeśli nie musisz zaczynać od zer, możesz użyć np.empty do niewielkiego zwiększenia prędkości.

Zobacz Cython dla użytkowników NumPy tutorial w dokumentach (Poprawiono go do poprawnego linku).

 3
Author: dwf,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-06-17 05:19:27

Powinieneś sprawdzić Ctypes jest to prawdopodobnie najprostsza rzecz w użyciu, jeśli chcesz tylko jednej funkcji.

 2
Author: Yon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-06-15 14:53:28