2015-01-07 43 views
3

我正在封裝一個C文件,所以我可以在python中使用它。 C函數的輸出是雙精度數組。我希望這是python中的一個numpy數組。我得到垃圾。這是一個生成錯誤的例子。用C擴展python,返回numpy數組給垃圾

首先,C文件(集中在最後一個函數的定義,一切應該是OK):

#include <Python.h> 
#include <numpy/arrayobject.h> 
#include <stdio.h> 

static char module_docstring[] = 
    "docstring"; 

static char error_docstring[] = 
     "generate the error"; 

static PyObject *_aux_error(PyObject *self, PyObject *args); 

static PyMethodDef module_methods[] = { 
     {"error", _aux_error, METH_VARARGS, error_docstring}, 
     {NULL, NULL, 0, NULL} 
}; 

PyMODINIT_FUNC init_tmp(void) { 

    PyObject *m = Py_InitModule3("_tmp", module_methods, module_docstring); 
    if (m == NULL) 
     return; 

    /* Load `numpy` functionality. */ 
    import_array(); 
} 

static PyObject *_aux_error(PyObject *self ,PyObject *args) { 

    double vector[2] = {1.0 , 2.0}; 

    npy_intp dims[1] = { 2 }; 

    PyObject *ret = PyArray_SimpleNewFromData(1, dims, (int)NPY_FLOAT , vector); 
    return ret; 
} 

編譯進OK(從我的理解 - 我使用的編譯一切python腳本)。

在Python中,我運行下面的腳本來測試我的新模塊:

try: 
    import _tmp 
    res = _tmp.error() 
    print(res) 
except: 
    print("fail") 

我在屏幕上看到的結果是垃圾。我試着用(int)NPY_FLOAT代替(int)NPY_FLOAT32, (int)NPY_FLOAT64, (int)NPY_DOUBLE,我仍然得到垃圾。 我正在使用python2.7。

謝謝!

編輯:下面的答案之後,我改變了最後一個功能:

static PyObject *_aux_error(PyObject *self, PyObject *args) { 


    double *vector = calloc(2, sizeof(double)); 
    vector[0] = 1.0; 
    vector[1] = 2.0; 


    npy_intp *dims = calloc(1 , sizeof(npy_intp)); 
    dims[1] = 2; 


    PyObject *ret = PyArray_SimpleNewFromData(1, dims, (int)NPY_FLOAT , &vector); 
    return ret; 
} 

現在蟒蛇顯示一個空數組。

+0

您編輯的調用應該是'PyArray_SimpleNewFromData(1,dims,NPY_DOUBLE,vector)'。它不是這樣工作嗎? – Jaime

+1

'dims [1] = 2'是一個索引錯誤 –

回答

5

嘗試修改此:

static PyObject *_aux_error(PyObject *self) { 

這樣:

static PyObject *_aux_error(PyObject *self, PyObject *args) { 

Python將通過args說法,即使你不使用它定義的功能。

您的代碼仍然存在一個基本問題。您已經使用堆棧上的數組vector創建了一個numpy數組。當_aux_error返回時,該內存將被回收並可能被重新使用。

你可以使用PyArray_SimpleNew()撥出numpy的數組創建數組,然後vector複製到陣列的數據:

static PyObject *_aux_error(PyObject *self, PyObject *args) 
{ 
    double vector[2] = {1.0 , 2.0}; 
    npy_intp dims[1] = {2}; 

    PyObject *ret = PyArray_SimpleNew(1, dims, NPY_DOUBLE); 
    memcpy(PyArray_DATA(ret), vector, sizeof(vector)); 
    return ret; 
} 

注意,我改變了類型NPY_DOUBLE; NPY_FLOAT是32位浮點類型。


在評論中,您問到了關於在_aux_error中動態分配內存的問題。以下是可能有用的示例變體。數組的長度仍然在dims中硬編碼,因此它不是一般的,但它可能足以解決評論中的問題。

static PyObject *_aux_error(PyObject *self, PyObject *args) 
{ 
    double *vector; 
    npy_intp dims[1] = {5}; 
    npy_intp k; 

    PyObject *ret = PyArray_SimpleNew(1, dims, NPY_DOUBLE); 
    vector = (double *) PyArray_DATA(ret); 
    /* 
    * NOTE: Treating PyArray_DATA(ret) as if it were a contiguous one-dimensional C 
    * array is safe, because we just created it with PyArray_SimpleNew, so we know 
    * that it is, in fact, a one-dimensional contiguous array. 
    */ 
    for (k = 0; k < dims[0]; ++k) { 
     vector[k] = 1.0 + k; 
    } 
    return ret; 
} 
+0

感謝您的輸入,但這沒有幫助。不應該通過刪除* args並且不給函數任何變量來簡化這個例子嗎? –

+1

否 - python解釋器不知道您已經使用非標準簽名定義了'_aux_error'。它將用兩個C參數來調用。 –

+1

請注意,我更新了我的答案,並提供了有關您的示例的基本問題的評論。 –

1

這是我的完整解決方案,爲您的娛樂。複製,粘貼和修改。顯然,我面臨的問題比上述問題稍微複雜一些。我用了一些 Dan Foreman Mackay's online code

我的代碼的目標是返回一個協方差矢量(不管是什麼)。我有一個名爲aux.c C文件,返回一個新分配的數組:

#include "aux.h" 
#include <math.h> 
#include <stdlib.h> 
double *covVec(double *X, double *x, int nvecs, int veclen) { 


    double r = 1.3; 
    double d = 1.0; 

    double result; 
    double dist; 
    int n; 

    double *k; 
    k = malloc(nvecs * sizeof(double)); 

    int row; 
    for(row = 0 ; row < nvecs ; row++) { 

     result = 0.0; 
     for (n = 0; n < veclen; n++) { 
       dist = x[n] - X[row*veclen + n]; 
       result += dist * dist; 
     } 

     result = d*exp( -result/(2.0*r*r) ); 
     k[row] = result; 
    } 
    return k; 
} 

然後,我需要一個名爲aux.h很短的頭文件:

double *covVec(double *X, double *x, int nvecs, int veclen); 

爲了把這個包到Python我有_aux.c

#include <Python.h> 
#include <numpy/arrayobject.h> 
#include "aux.h" 
#include <stdio.h> 

static char module_docstring[] = 
    "This module provides an interface for calculating covariance using C."; 

static char cov_vec_docstring[] = 
    "Calculate the covariances between a vector and a list of vectors."; 

static PyObject *_aux_covVec(PyObject *self, PyObject *args); 

static PyMethodDef module_methods[] = { 
     {"cov_vec", _aux_covVec, METH_VARARGS, cov_vec_docstring}, 
     {NULL, NULL, 0, NULL} 
}; 

PyMODINIT_FUNC init_aux(void) { 

    PyObject *m = Py_InitModule3("_aux", module_methods, module_docstring); 
    if (m == NULL) 
     return; 

    /* Load `numpy` functionality. */ 
    import_array(); 
} 


static PyObject *_aux_covVec(PyObject *self, PyObject *args) 
{ 
    PyObject *X_obj, *x_obj; 

    /* Parse the input tuple */ 
    if (!PyArg_ParseTuple(args, "OO", &X_obj, &x_obj)) 
     return NULL; 

    /* Interpret the input objects as numpy arrays. */ 
    PyObject *X_array = PyArray_FROM_OTF(X_obj, NPY_DOUBLE, NPY_IN_ARRAY); 
    PyObject *x_array = PyArray_FROM_OTF(x_obj, NPY_DOUBLE, NPY_IN_ARRAY); 


    /* If that didn't work, throw an exception. */ 
    if (X_array == NULL || x_array == NULL) { 
     Py_XDECREF(X_array); 
     Py_XDECREF(x_array); 
     return NULL; 
    } 

    /* What are the dimensions? */ 
    int nvecs = (int)PyArray_DIM(X_array, 0); 
    int veclen = (int)PyArray_DIM(X_array, 1); 
    int xlen = (int)PyArray_DIM(x_array, 0); 

    /* Get pointers to the data as C-types. */ 
    double *X = (double*)PyArray_DATA(X_array); 
    double *x = (double*)PyArray_DATA(x_array); 


    /* Call the external C function to compute the covariance. */ 
    double *k = covVec(X, x, nvecs, veclen); 



    if (veclen != xlen) { 
     PyErr_SetString(PyExc_RuntimeError, 
           "Dimensions don't match!!"); 
     return NULL; 
    } 

    /* Clean up. */ 
    Py_DECREF(X_array); 
    Py_DECREF(x_array); 

    int i; 
    for(i = 0 ; i < nvecs ; i++) { 
     printf("k[%d] = %f\n",i,k[i]); 
     if (k[i] < 0.0) { 
      PyErr_SetString(PyExc_RuntimeError, 
         "Covariance should be positive but it isn't."); 
      return NULL; 
     } 
    } 

    npy_intp dims[1] = {nvecs}; 

    PyObject *ret = PyArray_SimpleNew(1, dims, NPY_DOUBLE); 
    memcpy(PyArray_DATA(ret), k, nvecs*sizeof(double)); 
    free(k); 

    return ret; 
} 

我有一個python文件名爲setup_cov.py

from distutils.core import setup, Extension 
import numpy.distutils.misc_util 

setup(
    ext_modules=[Extension("_aux", ["_aux.c", "aux.c"])], 
    include_dirs=numpy.distutils.misc_util.get_numpy_include_dirs(), 
) 

我使用python2.7 setup_cov.py build_ext --inplace從命令行進行編譯。 然後我運行下面的Python測試文件:

import numpy as np 
import _aux as a 

nvecs = 6 
veclen = 9 
X= [] 
for _ in range(nvecs): 
    X.append(np.random.normal(size= veclen)) 
X = np.asarray(X) 

x = np.random.normal(size=veclen) 
k = a.cov_vec(X,x) 
print(k) 
1

沃倫的解決方案似乎是工作,但釋放C數組的內存塊導致錯誤的編輯對我來說。我得到了memcopy技巧來處理下面的簡約函數(通過指針將一維C數組複製到numpy),爲簡單起見,它沒有任何參數,應該給讀者一個好主意,如何將它應用到C數組而不是載體:

static PyObject *_cmod_test(PyObject *self, PyObject *args) 
    { 
    double f[5] = {0,1,2,3,4}; 
    int d[1] = {5}; 
    PyObject *c = PyArray_FromDims(1,d,NPY_DOUBLE); 
    memcpy(PyArray_DATA(c), f, 5*sizeof(double)); 
    return c;  
    }; 

的launch.py​​腳本是簡單

import _cmod 
_cmod.test() 

不要忘了申報功能

#include <Python.h> 
#include <numpy/arrayobject.h> 
#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION 
static PyObject *_cmod_test(PyObject *self, PyObject *args); 

任何建議與使用PyArray_SimpleNewFromData(同時避免內存泄漏pittfall)?也許類似於下面的破碎的代碼。

static PyObject *_cmod_test(PyObject *self, PyObject *args) 
    { 
    double f[5] = {0,1,2,3,4}; 
    npy_intp dims[1] = {5}; 
    PyObject *c = PyArray_SimpleNewFromData(1, dims, NPY_DOUBLE ,f); 
    PyArray_ENABLEFLAGS(c, NPY_ARRAY_OWNDATA); 
    return c; 
    }; 

我也推薦Dan Foreman Mackay在python C API上的博客。