我正在尝试传递不同的函数,这些函数将指针作为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
答案 0 :(得分:1)
要处理的第一件事是签名cdef void (*func)(double *, double *, double *)
的函数不会传递数组长度。您无法知道这些数组有多长,因此您无法安全地访问其元素。明智的做法是改变函数签名以传递长度:
cdef void (*func)(double *, double *, double *, int)
更令人困惑的是,您似乎在normal
和sample
中迭代一维数组的同一轴。我怀疑这不是你想要做的,但我不会试图解决这个问题。
基本上你的问题是你想要将任意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函数,那么有一个更简单但更有限的解决方案。
这是一个演示如何实现这个想法的最小例子:
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访问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 # ...
)