【问题标题】:Simple wrapping of C code with cython用 cython 简单包装 C 代码
【发布时间】:2011-03-04 01:02:43
【问题描述】:

我有许多 C 函数,我想从 python 调用它们。 cython 似乎是要走的路,但我真的找不到一个例子来说明这是如何完成的。我的 C 函数如下所示:

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 数组(或 python 列表。所有双精度数组都有 N 个元素)。我的代码假定指针指向已分配的内存块。此外,生成的 C 代码应该链接到一些外部库。

【问题讨论】:

  • 我最近刚刚使用 Cython 打包了我的 C 库,您可能想看一下如何做的示例。我在这里详细解释了整个过程,包括构建和分发模块:martinsosic.com/development/2016/02/08/…

标签: python numpy cython


【解决方案1】:

您应该查看Ctypes,如果您只需要一个功能,这可能是最容易使用的东西。

【讨论】:

  • 是的,但我想稍后用 cython 包装其他东西,所以这是我的起点:)
  • 即使对小型包装器使用 ctypes 也是危险和脆弱的,因为该通用方法存在缺陷(不使用头文件等)。
  • 这个问题是关于 Cython 的;这个问题无法回答这个问题。
【解决方案2】:

基本上你可以编写你的 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 教程(将其固定到正确的链接)。

【讨论】:

    【解决方案3】:

    这是一个传递 numpy 数组的小而完整的示例 到外部 C 函数,逻辑上

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

    使用 Cython。 (这对于熟悉它的人来说肯定是众所周知的。 欢迎评论。 最后一次更改:2011 年 2 月 23 日,适用于 Cython 0.14。)

    初读或略读 Cython buildCython with NumPy

    两步:

    • python f-setup.py build_ext --inplace
      转为f.pyxfc.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.cppfc.cpp
    链接f.o fc.o -> f.so, python import f 将加载的动态库。

    对于学生,我建议:制作这些步骤的图表, 查看下面的文件,然后下载并运行它们。

    (distutils 是一个庞大而复杂的包,用于 制作用于分发的 Python 包,并安装它们。 在这里,我们只使用它的一小部分来编译并链接到f.so。 此步骤与 Cython 无关,但可能会造成混淆; .pyx 中的简单错误可能会导致来自 g++ 编译和链接的晦涩错误消息页面。 也可以看看 distutils doc 和/或 SO questions on distutils.)

    喜欢makesetup.py 将重新运行 cython f.pyxg++ -c ... f.cpp 如果f.pyxf.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 usersSO 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 \
        "$@" \
        -o $base.so
    
    # undefs: nm -gpv $base.so | egrep '^ *U _+[^P]'
    !
    
    # 23 Feb 2011 13:38
    

    【讨论】:

    • 真的没有必要使用带有char* 指针的包装函数。您可以直接在 Cython 中包装 fcreal() 并将其称为 fcret = fcreal( N, A.data, B.data, &lt;double*&gt; Z.data )。此外,单独编译fc.o 容易出错且不可移植。只需在sources= 中包含fc.cpp
    • 如果传递的 numpy 数组在内存中不连续或具有 Fortran 字节顺序,这可能会产生意外结果。此外,所需的演员阵容有点讨厌。有关更好的 cython 代码,请参见下文。
    • @denis 我有一个Cython post,您或许可以提供见解。
    【解决方案4】:

    以下 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])
    

    【讨论】:

    • 这是否会导致额外的内存复制(如果数组已经是连续的)?
    • @dashesy: 不,如果数组已经是连续的,则没有额外的副本。
    猜你喜欢
    • 2020-09-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-07-07
    • 2021-04-27
    • 1970-01-01
    相关资源
    最近更新 更多