2010-06-15 7 views
41

Tôi có một số hàm C và tôi muốn gọi chúng từ python. cython dường như là con đường để đi, nhưng tôi không thể thực sự tìm thấy một ví dụ về cách chính xác điều này được thực hiện. chức năng C của tôi trông như thế này:.Gói đơn giản mã C với 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) ; 

Tất cả tôi muốn làm là để xác định ba thông số đầu tiên (một chuỗi và hai số nguyên), và phục hồi 8 mảng NumPy (hoặc danh sách python Tất cả các mảng đôi có N yếu tố). Mã của tôi giả định rằng các con trỏ trỏ đến một đoạn bộ nhớ đã được phân bổ. Ngoài ra, mã C được tạo ra phải liên kết với một số thư viện bên ngoài.

+0

Tôi chỉ vừa mới bọc C thư viện của tôi sử dụng Cython, bạn có thể muốn tham gia một hãy xem điều đó để biết ví dụ về cách thực hiện. Tôi đã giải thích toàn bộ quy trình chi tiết ở đây, bao gồm xây dựng và phân phối mô-đun: http://martinsosic.com/development/2016/02/08/wrapping-c-library-as-python-module.html. – Martinsos

Trả lời

63

Dưới đây là một ví dụ nhỏ nhưng hoàn toàn đi qua mảng NumPy đến một chức năng C bên ngoài, một cách logic

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

sử dụng Cython. (Điều này chắc chắn là nổi tiếng cho những ai biết nó cũng Comments đều được chào đón thay đổi cuối:... Ngày 23 tháng 2 năm 2011, cho Cython 0.14)

Đầu đọc hoặc đọc lướt Cython buildCython with NumPy.

2 bước sau:

  • python f-setup.py build_ext --inplace
    biến f.pyxfc.cpp ->f.so, thư viện động
  • python test-f.py
    import f tải f.so; f.fpy(...) gọi C fc(...).

python f-setup.py sử dụng distutils để chạy cython, biên dịch và liên kết:
cython f.pyx -> f.cpp
biên dịch f.cppfc.cpp
liên kết f.o fc.o ->f.so, một động lib rằng python import f sẽ được tải.

Đối với sinh viên, tôi khuyên bạn nên: lập sơ đồ các bước này, xem qua các tệp bên dưới, sau đó tải xuống và chạy chúng.

(distutils là một gói phức tạp khổng lồ dùng để gói Python làm cho phân phối và cài đặt chúng. Ở đây chúng ta đang sử dụng chỉ là một phần nhỏ của nó để biên dịch và liên kết đến f.so. Bước này không có gì để làm với Cython, nhưng nó có thể được khó hiểu;.. sai lầm đơn giản trong một .pyx có thể gây ra các trang thông báo lỗi khó hiểu từ g ++ biên dịch và liên kết Xem thêm distutils doc và/hoặc SO questions on distutils)

Giống như make, setup.py sẽ chạy lại cython f.pyxg++ -c ... f.cpp nếu f.pyx mới hơn f.cpp.
Để dọn dẹp, rm -r build/.

Một thay thế cho setup.py sẽ chạy các bước riêng biệt, trong một kịch bản hay 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.
Sửa đổi các wrapper cc-lib-mac dưới đây cho nền tảng và cài đặt của bạn: nó không đẹp, nhưng nhỏ.

Ví dụ thực sự về gói C Cython C, xem các tệp .pyx chỉ trong khoảng SciKit.

Xem thêm: Cython for NumPy usersSO questions/tagged/cython.


Để giải nén các tập tin sau đây, cắt dán rất nhiều vào một tập tin lớn, nói cython-numpy-c-demo, sau đó trong Unix (trong một thư mục mới sạch) chạy 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

Nó thực sự không cần thiết để sử dụng một hàm wrapper có một con trỏ 'char *'. Bạn có thể bọc 'fcreal()' trực tiếp trong Cython và gọi nó là 'fcret = fcreal (N, A.data, B.data, Z.data)'. Ngoài ra, nó dễ bị lỗi và không thể di chuyển để biên dịch 'fc.o' một cách riêng biệt. Chỉ cần bao gồm 'fc.cpp' trong' sources = '. – oceanhug

+1

Điều này có thể sẽ tạo ra kết quả không mong muốn nếu mảng numpy được truyền không liên tục trong bộ nhớ hoặc có thứ tự byte Fortran. Ngoài ra, các diễn viên yêu cầu là một chút khó chịu. Xem bên dưới để biết mã cython tốt hơn. – Nikratio

+0

@denis Tôi có [bài đăng Cython] (http://stackoverflow.com/questions/41944883/verifying-compatibility-in-compiling-extension-types-and-using-them-with-cdef) bạn có thể cung cấp thông tin chi tiết về. – Phillip

2

Bạn nên kiểm tra Ctypes nó có lẽ là điều dễ dàng nhất để sử dụng nếu tất cả những gì bạn muốn là một hàm.

+0

Đúng, nhưng tôi muốn bọc các thứ khác bằng cách sử dụng cython sau, vì vậy đây là điểm bắt đầu của tôi :) – Jose

+3

Sử dụng ctypes ngay cả đối với các trình bao bọc nhỏ là nguy hiểm và mong manh vì các lỗ hổng của phương pháp chung đó (không sử dụng các tệp tiêu đề và vì vậy ra). –

+0

Câu hỏi hỏi về Cython; câu hỏi này không có gì để trả lời câu hỏi đó. –

3

Về cơ bản, bạn có thể viết hàm Cython của bạn như vậy mà nó phân bổ các mảng (chắc chắn rằng bạn cimport numpy as np):

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

sau đó vượt qua trong con trỏ .data của mỗi chức năng C của bạn. Cần làm việc. Nếu bạn không cần phải bắt đầu với số không, bạn có thể sử dụng np.empty để tăng tốc độ nhỏ.

Xem hướng dẫn Cython for NumPy Users trong tài liệu (cố định nó vào liên kết chính xác).

12

sau Mã Cython từ http://article.gmane.org/gmane.comp.python.cython.user/5625 không đòi hỏi diễn viên rõ ràng và cũng xử lý các mảng không liên tục:

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

Điều này có dẫn đến một bản sao bộ nhớ thừa (nếu mảng đã tiếp giáp) không? – dashesy

+0

@dashesy: ​​không, nếu mảng đã được tiếp giáp, không có thêm bản sao. – Nikratio

+0

Nếu A_c là 1-D, có đúng để chuyển thành fc (& A_c [0]) không? – ascetic652