我不确定你是否可以更改函数指针类型
ctypedef int (*function_type)(int a, int b)
到
ctypedef int (*function_type)(int a, int b, void *func_d)
但这通常是回调函数在C中实现的方式。void *参数func_d函数包含用户提供的任何形式的数据。如果答案是肯定的,那么您可以有以下解决方案。
首先,在 Cython 中创建以下定义文件,以向其他 Cython 用户显示您的 C API:
# binary_op.pxd
ctypedef int (*func_t)(int a, int b, void *func_d) except? -1
cdef int func(int a, int b, void *func_d) except? -1
cdef class BinaryOp:
cpdef int eval(self, int a, int b) except? -1
cdef class Add(BinaryOp):
cpdef int eval(self, int a, int b) except? -1
cdef class Multiply(BinaryOp):
cpdef int eval(self, int a, int b) except? -1
这基本上允许任何 Cython 用户 cimport 这些定义直接进入他们的 Cython 代码并绕过任何与 Python 相关的函数调用。然后,您在以下pyx 文件中实现该模块:
# binary_op.pyx
cdef int func(int a, int b, void *func_d) except? -1:
return (<BinaryOp>func_d).eval(a, b)
cdef class BinaryOp:
cpdef int eval(self, int a, int b) except? -1:
raise NotImplementedError()
cdef class Add(BinaryOp):
cpdef int eval(self, int a, int b) except? -1:
return a + b
cdef class Multiply(BinaryOp):
cpdef int eval(self, int a, int b) except? -1:
return a * b
def call_me(BinaryOp oper not None, c, d):
return func(c, d, <void *>oper)
如您所见,BinaryOp 充当基类,它为未正确实现eval 的用户引发NotImplementedError。 cpdef 函数可以被 Cython 和 Python 用户覆盖,如果从 Cython 调用它们,则涉及高效的 C 调用机制。否则,从 Python 调用时会产生很小的开销(当然,这些函数在标量上工作,因此开销可能不会那么小)。
那么,Python 用户可能拥有以下应用程序代码:
# app_1.py
import pyximport
pyximport.install()
from binary_op import BinaryOp, Add, Multiply, call_me
print(call_me(Add(), 5, 6))
print(call_me(Multiply(), 5, 6))
class LinearOper(BinaryOp):
def __init__(self, p1, p2):
self.p1 = p1
self.p2 = p2
def eval(self, a, b):
return self.p1 * a + self.p2 * b
print(call_me(LinearOper(3, 4), 5, 6))
如您所见,它们不仅可以从高效的 Cython(具体)类(即 Add 和 Multiply)创建对象,还可以基于 BinaryOp 实现自己的类(希望通过提供实现eval)。当你运行python app_1.py 时,你会看到(编译后):
11
30
39
然后,您的 Cython 用户可以实现他们喜欢的功能,如下所示:
# sub.pyx
from binary_op cimport BinaryOp
cdef class Sub(BinaryOp):
cpdef int eval(self, int a, int b) except? -1:
return a - b
当然,任何使用sub.pyx 的应用程序代码都可以使用这两个库,如下所示:
import pyximport
pyximport.install()
from sub import Sub
from binary_op import call_me
print(call_me(Sub(), 5, 6))
当您运行python app_2.py 时,您会得到预期的结果:-1。
编辑。顺便说一句,如果您被允许拥有上述function_type 签名(即,具有void * 参数作为第三个参数的签名),您can 实际上将任意 Python 可调用对象作为 C 指针传递。为此,您需要进行以下更改:
# binary_op.pyx
cdef int func(int a, int b, void *func_d) except? -1:
return (<object>func_d)(a, b)
def call_me(oper not None, c, d):
return func(c, d, <void *>oper)
但是请注意,Python 现在需要确定 object oper 是哪个。在前一个解决方案中,我们将 oper 限制为有效的 BinaryOp 对象。另请注意,__call__ 和类似的特殊功能只能声明为def,这会限制您的用例。不过,通过这些最后的更改,我们可以毫无问题地运行以下代码:
print(call_me(lambda x, y: x - y, 5, 6))