使用从Cython中的方法创建的PyCapsule的错误结果

时间:2017-12-03 20:59:46

标签: python cython pythran

我们需要在Cython中从类的方法创建一个PyCapsule。我们设法编写了一个代码,它编译甚至运行没有错误,但结果是错误的。

这里有一个简单的例子:https://github.com/paugier/cython_capi/tree/master/using_cpython_pycapsule_class

胶囊由Pythran执行(需要使用github上的版本https://github.com/serge-sans-paille/pythran)。

.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(call_capsule_pythran.py)编译的文件。

# pythran export call_capsule(int(int), int)

def call_capsule(capsule, n):
    r = capsule(n)
    return r

再一次,这是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)创建一个带有签名int(int)的C函数来自绑定方法{{1其签名实际为twice_cdef

我不知道Cython是否真的无法做到......

1 个答案:

答案 0 :(得分:2)

跟进/扩展我的评论:

基本问题是Pythran期望一个带有签名int f(int)的C函数指针包含在PyCapsule中。但是,您的方法的签名是int(PyObject* self, int c)2作为self传递(因为它实际上没有被使用而导致灾难......)并且使用了一些任意位的内存来代替int c。不幸的是,不可能使用纯C代码来创建带有“绑定参数”的C函数指针,因此Cython不能(并且实际上不能)执行它。

修改1是通过创建一个接受正确类型并在其中进行转换的函数来获得更好的编译时类型检查,以便传递给PyCapsules的内容,而不是仅仅转换为{{1}盲目地。这不能解决您的问题,但会在编译时警告您无法正常工作:

<void*>

实际上可以使用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 (或ctypes)从任意Python callables创建C函数 - 请参阅Using function pointers to methods of classes without the gil(答案的底部)。这增加了额外的Python调用层,因此速度不是很快,而且代码有点混乱。 cffi通过使用运行时代码生成(不是那种可移植代码或者你可以在纯C中执行的操作)来实现这一点,以便动态构建函数,然后创建指向它的指针。

虽然您在评论中声称您不认为可以使用Python解释器,但我认为这不是真的 - Pythran生成Python扩展模块(因此非常类似于Python解释器)并且它似乎在你的测试用例中工作:

ctypes

不幸的是,它仅适用于 _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)') 而不是cpdef函数,因为它依赖于Python可调用。 cdef函数可以与lambda一起使用(前提是您将cdef更改为get_capi而不是def):

cpdef

这有点乱,但可以使用。