Python 使用Cython中的方法创建的PyCapsule产生错误结果

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

我们需要从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_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)'),