Python 使用Cython中的方法创建的PyCapsule产生错误结果
我们需要从Cython中的类的方法创建一个PyCapsule。我们成功地编写了一段代码,它编译甚至运行时都没有错误,但结果是错误的 这里有一个简单的例子: 胶囊由Pytran执行(需要使用github上的版本) .pyx文件:Python 使用Cython中的方法创建的PyCapsule产生错误结果,python,cython,pythran,Python,Cython,Pythran,我们需要从Cython中的类的方法创建一个PyCapsule。我们成功地编写了一段代码,它编译甚至运行时都没有错误,但结果是错误的 这里有一个简单的例子: 胶囊由Pytran执行(需要使用github上的版本) .pyx文件: from cpython.pycapsule cimport PyCapsule_New cdef int twice_func(int c): return 2*c cdef class Twice: cdef public dict __pyx
from cpython.pycapsule cimport PyCapsule_New
cdef int twice_func(int c):
return 2*c
cdef class Twice:
cdef public dict __pyx_capi__
def __init__(self):
self.__pyx_capi__ = self.get_capi()
cpdef get_capi(self):
return {
'twice_func': PyCapsule_New(
<void *>twice_func, 'int (int)', NULL),
'twice_cpdef': PyCapsule_New(
<void *>self.twice_cpdef, 'int (int)', NULL),
'twice_cdef': PyCapsule_New(
<void *>self.twice_cdef, 'int (int)', NULL),
'twice_static': PyCapsule_New(
<void *>self.twice_static, 'int (int)', NULL)}
cpdef int twice_cpdef(self, int c):
return 2*c
cdef int twice_cdef(self, int c):
return 2*c
@staticmethod
cdef int twice_static(int c):
return 2*c
这又是Pythran的一个新特性,因此需要github上的版本
和测试文件:
try:
import faulthandler
faulthandler.enable()
except ImportError:
pass
import unittest
from twice import Twice
from call_capsule_pythran import call_capsule
class TestAll(unittest.TestCase):
def setUp(self):
self.obj = Twice()
self.capi = self.obj.__pyx_capi__
def test_pythran(self):
value = 41
print('\n')
for name, capsule in self.capi.items():
print('capsule', name)
result = call_capsule(capsule, value)
if name.startswith('twice'):
if result != 2*value:
how = 'wrong'
else:
how = 'good'
print(how, f'result ({result})\n')
if __name__ == '__main__':
unittest.main()
它有四轮马车,并提供:
capsule twice_func
good result (82)
capsule twice_cpdef
wrong result (4006664390)
capsule twice_cdef
wrong result (4006664390)
capsule twice_static
good result (82)
结果表明,该方法对标准函数和静态函数都很有效,但在方法上存在问题
请注意,它对两个胶囊有效的事实似乎表明问题并非来自Pythran
编辑
在DavidW的评论之后,我了解到我们必须在运行时(例如在get_capi
中)创建一个C函数,该函数的签名int(int)
来自绑定方法tweep\cdef
,其签名实际上是int(tweep,int)
我不知道这对Cython来说是否真的是不可能的…要跟进/扩展我的评论: 基本问题是Pytran希望Pytark中包含签名为
intf(int)
的C函数指针。但是,方法的签名是int(PyObject*self,intc)
。2
作为self
传递(由于未实际使用,因此不会导致灾难…),并使用一些任意内存位来代替int c
。不幸的是,不可能使用纯C代码创建带有“绑定参数”的C函数指针,因此Cython不能(实际上也不能)这样做
修改1是通过创建一个接受正确类型并在其中强制转换的函数,而不是盲目地强制转换到
,从而更好地对传递给的内容进行编译时类型检查。这并不能解决您的问题,但会在编译时警告您该问题不起作用:
ctypedef int(*f_ptr_type)(int)
cdef make_PyCapsule(f_ptr_type f, string):
return PyCapsule_New(
<void *>f, string, NULL)
# then in get_capi:
'twice_func': make_PyCapsule(twice_func, b'int (int)'), # etc
不幸的是,它只适用于cpdef
函数,而不适用于cdef
函数,因为它确实依赖于Python的可调用性cdef
功能可以与lambda一起使用(前提是您将get_capi
更改为def
,而不是cpdef
):
它有点凌乱,但可以让它工作。我对调用(call_capsule)
有点困惑-你说它使用Pyran,但你在github中得到的代码(请放在这里,这样就有一个现场…)只是像调用Python对象一样调用capsule,我真的认为它不应该工作。我并不奇怪这是失败的,但我很惊讶它在任何情况下都能工作。我需要在这里复制粘贴文件Makefile和setup.py吗?啊,好的,我想这更清楚了。我已经快速查看了。我认为最基本的问题是方法的签名是int(PyObject*self,intc)
。2被传递为self
(由于没有实际使用,因此不会导致灾难…),一些任意内存位被用作c
。不幸的是,创建带有“绑定参数”的C函数指针是不可能的,所以Cython无法(也将无法)这样做。我知道的唯一方法是使用ctypes
/cffi
,但这有点混乱,并且增加了一层Python调用-请参阅(答案的底部)。(如果需要的话,我可能会在以后有时间的时候添加一个真实的答案,但希望这个提示现在会有用)
ctypedef int(*f_ptr_type)(int)
cdef make_PyCapsule(f_ptr_type f, string):
return PyCapsule_New(
<void *>f, string, NULL)
# then in get_capi:
'twice_func': make_PyCapsule(twice_func, b'int (int)'), # etc
_func_cache = []
cdef f_ptr_type py_to_fptr(f):
import ctypes
functype = ctypes.CFUNCTYPE(ctypes.c_int,ctypes.c_int)
ctypes_f = functype(f)
_func_cache.append(ctypes_f) # ensure references are kept
return (<f_ptr_type*><size_t>ctypes.addressof(ctypes_f))[0]
# then in make_capi:
'twice_cpdef': make_PyCapsule(py_to_fptr(self.twice_cpdef), b'int (int)')
'twice_cdef': make_PyCapsule(py_to_fptr(lambda x: self.twice_cdef(x)), b'int (int)'),