通过不同的" C"函数使用指针数组作为类的函数参数

时间:2017-09-26 10:05:22

标签: python c class pointers cython

我正在尝试传递不同的函数,这些函数将指针作为python函数的参数。输入函数作为输入参数的一个示例是给定的`12564.0 + 3193.0 + 4545.0` 函数:

Sample.pyx

normal

test.py

from cpython cimport array
import cython
import ctypes
cimport numpy as np
cpdef void normal(np.ndarray[ndim=1, dtype=np.float64_t] u, 
                  np.ndarray[ndim=1, dtype=np.float64_t] yu, 
                  np.ndarray[ndim=1, dtype=np.float64_t] ypu):

      cdef int i
      cdef int n=len(u)
      for i in prange(n, nogil=True):
          yu[i]=-u[i]*u[i]*0.5                                                               
          ypu[i]=-u[i]                                                                    
      return                                                     
cdef class _SampleFunc:
     cdef void (*func)(double *, double *, double *)

cdef void sample(int* x, double* hx, double* hxx, void(*func)(double*, double*, double*), int n):
      def int i
      for i from 0 <= i < n:
          func[0](&x[i], &hx[i], &hxx[i])
      return 
cdef class myClass:
     sample_wrapper = _SampleFunc() 
     sample_wrapper.func = Null
     def foo(np.ndarray[ndim=1, dtype=np.float64_t] x,
             np.ndarray[ndim=1, dtype=np.float64_t] hx,
             np.ndarray[ndim=1, dtype=np.float64_t] hxx,
             _SampleFunc sample_func, 
             int k):
         cdef np.ndarray[ndim=1, dtype=np.float64_t] sp
         cdef int num=len(x)
         func = sample_func.func
         assert func is not NULL, "function value is NULL"
         cdef int j
         for j from 0 <= j <k:
             sample(&x[0],&hx[0], &hxx[0], func, num)
             sp[j]=hx[0]
         return sp

运行import numpy as np from sample import * x = np.zeros(10, float) hx = np.zeros(10, float) hpx = np.zeros(10, float) x[0] = 0 x[1] = 1.0 x[2] = -1.0 def pynormal(x): return -x*x*0.5,-x hx[0], hpx[0] = pynormal(x[0]) hx[1], hpx[1] = pynormal(x[1]) hx[2], hpx[2] = pynormal(x[2]) num=20 ars=myClass() s=ars.foo( x, hx, hpx, normal, num) 代码我收到此错误:

test.py

我正在尝试为不同的'ars._SampleFunc' object has no attribute 'func' 函数编写一个包装器,它们有三个指针数组作为参数。到目前为止我的结论是它可以用类完成,因为可以在python中访问类。我想知道如何将C函数与指针数组作为参数传递给C类?

更新:正常功能

myClass

1 个答案:

答案 0 :(得分:1)

要处理的第一件事是签名cdef void (*func)(double *, double *, double *) 的函数不会传递数组长度。您无法知道这些数组有多长,因此您无法安全地访问其元素。明智的做法是改变函数签名以传递长度:

cdef void (*func)(double *, double *, double *, int)

更令人困惑的是,您似乎在normalsample中迭代一维数组的同一轴。我怀疑这不是你想要做的,但我不会试图解决这个问题。

基本上你的问题是你想要将任意Python可调用作为C函数指针传递。坏消息是Cython不能这样做 - 一个Python可调用的信息有大量的信息,而一个C函数指针只是一些可执行存储器的地址。因此,C函数指针没有可用于保存Python可调用信息的空间。为了完成这项工作,您需要在运行时生成代码,而Python无法做到这一点。

I've recommended the ctypes standard library module as a solution to similar problems previously,因为它可以create a function pointer from a Python callable。如果您只想调用cdef Cython函数,那么有一个更简单但更有限的解决方案。

ctypes的

这是一个演示如何实现这个想法的最小例子:

import numpy as np
import ctypes

ctypedef void (*func_t)(int, double *)

cdef void sample(int n, double* x, func_t f):
    f(n,x)

def call_sample(double[::1] x,
                f):

    def func_wrapper(n, arg1):
        # x is a slightly opaque ctypes type
        # first cast it to a ctypes array of known size
        # and then create a numpy array from that
        arg1_as_ctypes_array = (ctypes.c_double*n).from_address(ctypes.addressof(arg1.contents))
        return f(np.asarray(arg1_as_ctypes_array))


    FTYPE = ctypes.CFUNCTYPE(None, # return type
                             ctypes.c_int, # arguments
                             ctypes.POINTER(ctypes.c_double))
    f_ctypes = FTYPE(func_wrapper) # convert Python callable to ctypes function pointer

    # a rather nasty line to convert to a C function pointer
    cdef func_t f_ptr = (<func_t*><size_t>ctypes.addressof(f_ctypes))[0]

    sample(x.shape[0], &x[0], f_ptr)


def example_function(x):
    # expects a numpy array like object
    print(x)

def test():
    a = np.random.rand(20)
    print(a)
    call_sample(a,example_function)

我意识到ctypes和Cython之间有一些稍微混乱的转换 - 这是不可避免的。

一点解释:我假设您希望保持Python界面简单,因此example_function只需要一个类似numpy数组的对象。 ctypes传递的函数需要接受许多元素和一个与C接口匹配的指针。

ctypes指针类型(LP_c_double)可以做索引(即arg1[5]),因此它适用于简单的用途,但它不会在内部存储它的长度。将它更改为numpy数组是有用的(但不是必需的),因此您可以更普遍地使用它,因此我们创建了一个包装函数来执行此操作。我们这样做:

arg1_as_ctypes_array = (ctypes.c_double*n).from_address(ctypes.addressof(arg1.contents))

将其转换为已知长度的ctypes数组,然后

np.asarray(arg1_as_ctypes_array)

将其转换为numpy数组。这会共享数据而不是复制数据,因此如果您更改数据,则原始数据将会更改。因为转换为numpy数组遵循标准模式,所以在call_sample中生成包装函数很容易。

(在评论中,如果您只是传递double而不是double*,则会询问如何进行转化。在这种情况下,您不必做任何事情因为ctypes double的行为与Python类型完全相同)

cdef个函数

如果您确定要传递的功能始终是cdef功能,那么您可以避免使用ctypes并提出更简单的功能。首先需要使函数签名与指针完全匹配:

cdef void normal(int N, double *x): # other parameters as necessary
    cdef double[::1] x_as_mview = <double[:N:1]>x # cast to a memoryview
    # ... etc

然后,您应该能够使用SampleFunc的定义来创建模块级对象:

# in Cython
normal_samplefunc = SampleFunc()
normal_samplefunc.func = &normal

# in Python
s=ars.foo( x, hx, hpx, normal_samplefunc, num)

ars.foo是您编写它的方式(没有ctypes代码):

func = sample_func.func
# ...
sample(..., func,...)

此代码运行得更快,但您希望能够从Python调用normal

Python界面

您在评论中提到,您也希望能够从Python访问normal。您可能需要为Python函数和传递给C的函数使用不同的接口,因此我为两种用途定义了一个单独的函数,但是共享实现:

def normal(double[::1] u, # ... other arguments
           ):
   # or cpdef, if you really want
   implementation goes here

# then, depending on if you're using ctypes or not:
def normal_ctypes(int n, u # other arguments ...
   ):
   u_as_ctypes_array = (ctypes.c_double*n).from_address(ctypes.addressof(x.contents))
   normal(u_as_ctypes_array, # other arguments
                )

# or
cdef void normal_c(int n, double* u # ...
              ):
   normal(<double[:N:1]>x # ...
          )