2010-06-15 3 views
41

У меня есть ряд функций C, и я хотел бы назвать их из python. cython, кажется, путь, но я не могу найти пример того, как именно это делается. Моя функция C выглядит следующим образом:.Простая упаковка кода C с помощью 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) ; 

Все, что я хочу сделать, это указать первые три параметра (строки и два целых числа), и восстановить 8 Numpy массивов (или списки питона Всех двойные массивы имеют N элементы). Мой код предполагает, что указатели указывают на уже выделенный фрагмент памяти. Кроме того, полученный код C должен быть связан с некоторыми внешними библиотеками.

+0

Я только недавно обернула C библиотека с помощью Cython, вы можете взять посмотрите на пример, как это сделать. Здесь я подробно объяснил весь процесс, включая создание и распространение модуля: http://martinsosic.com/development/2016/02/08/wrapping-c-library-as-python-module.html. – Martinsos

ответ

63

Вот небольшой, но полный пример прохождения Numpy массивов к внешней функции C, логически

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

использованием Cython. (Это, конечно, хорошо известно тем, кто знает его хорошо Комментарии приветствуются Последнее изменение:... 23 февраля 2011, для Cython 0,14)

Первое чтение или обезжиренное Cython build и Cython with NumPy.

2 шага:

  • python f-setup.py build_ext --inplace
    превращает f.pyx и fc.cpp ->f.so, динамическая библиотека
  • python test-f.py
    import f грузы f.so; f.fpy(...) называет C fc(...).

python f-setup.py использует distutils для запуска Cython, компилировать и ссылка:
cython f.pyx -> f.cpp
компилировать f.cpp и fc.cpp
ссылку f.o fc.o ->f.so, динамический Lib, что питон import f загрузит.

Для студентов я предлагаю: составить схему этих шагов, просмотреть файлы ниже, а затем загрузить и запустить их.

(distutils огромный, запутанный пакет используется для пакетов Python делают для распространения, и установить их. Здесь мы используем лишь малую часть его, чтобы собрать и ссылку на f.so. Этот шаг не имеет ничего делать с Cython, но это может быть запутанным,.. простые ошибки в .pyx может вызвать страницы непонятных сообщений об ошибках от г ++ компилируется и ссылки Смотрите также distutils doc и/или SO questions on distutils)

Как make, setup.py будет повторно запущен cython f.pyx и g++ -c ... f.cpp если f.pyx является новее, чем f.cpp.
Для очистки, rm -r build/.

Альтернативой setup.py будет запускать шаги отдельно, в сценарии или 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.
Измените обертку cc-lib-mac ниже для вашей платформы и установки: это не красиво, но мало.

Для реальных примеров упаковки Cython C, смотрите .pyx-файлы практически в любом SciKit.

См. Также: Cython for NumPy users и SO questions/tagged/cython.


Чтобы распаковать следующие файлы, вырезать-вставить много в один большой файл, скажем cython-numpy-c-demo, затем в Unix (в чистом новом каталоге) запустить 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

Нет необходимости использовать функцию-обертку, которая принимает указатель 'char *'. Вы можете обернуть 'fcreal()' непосредственно в Cython и называть его как 'fcret = fcreal (N, A.data, B.data, Z.data)'. Кроме того, он подвержен ошибкам и не переносится для компиляции 'fc.o' отдельно. Просто включите 'fc.cpp' в' sources = '. – oceanhug

+1

Это, вероятно, приведет к неожиданным результатам, если переданный массив numpy не является непрерывным в памяти или имеет порядок байтов Fortran. Кроме того, требуемый отбор немного неприятен. См. Ниже лучший код cython. – Nikratio

+0

@denis У меня есть [Cython post] (http://stackoverflow.com/questions/41944883/verifying-compatibility-in-compiling-extension-types-and-using-them-with-cdef), вы можете быть в состоянии обеспечить понимание. – Phillip

2

Вы должны проверить Ctypes, это, вероятно, самая простая вещь для использования, если вы хотите только одну функцию.

+0

Верно, но я хотел бы обернуть другой материал, используя cython позже, так что это моя отправная точка :) – Jose

+3

Использование ctypes даже для небольших оберток является опасным и хрупким из-за недостатков этого общего подхода (не используя файлы заголовков и т. Д. д). –

+0

Вопрос задает вопрос о Китоне; этот вопрос не отвечает на это. –

3

В принципе вы можете написать функцию Cython таким образом, что он выделяет массивы (убедитесь, что вы cimport numpy as np):

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

затем переходят в .data указатель каждой вашей функции C. Это должно сработать. Если вам не нужно начинать с нулей, вы можете использовать np.empty для небольшого повышения скорости.

См. Учебник Cython for NumPy Users в документах (исправлено его по правильной ссылке).

12

Следующая Cython код из http://article.gmane.org/gmane.comp.python.cython.user/5625 не требует явных приведений, а также обрабатывает не-непрерывные массивы:

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

Это приводит к дополнительной копии памяти (если массив уже смежный)? – dashesy

+0

@dashy: нет, если массив уже смежный, дополнительной копии нет. – Nikratio

+0

Если A_c является 1-D, правильно ли оно проходить как fc (& A_c [0])? – ascetic652

Смежные вопросы