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

2024-05-16 04:30:57 发布

您现在位置:Python中文网/ 问答频道 /正文

我们需要从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)。在

^{pr2}$

这又是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)来自绑定方法twice_cdef,其签名实际上是int(Twice, int)。在

我不知道这是不是真的不可能与赛顿。。。在


Tags: selfgithubnewreturnresultintfuncpyx
1条回答
网友
1楼 · 发布于 2024-05-16 04:30:57

跟进/扩展我的评论:

基本问题是pythoran希望PyCapsule中包含一个带有int f(int)签名的C函数指针。但是,方法的签名是int(PyObject* self, int c)2被传递为self(因为它实际上没有被使用,所以不会导致灾难…),并且使用一些任意的内存位来代替{}。不幸的是,不可能使用纯C代码创建带有“绑定参数”的C函数指针,所以Cython不能(实际上也不能)这样做。在

修改1是通过创建一个接受正确类型的函数并在其中进行强制转换,而不是盲目地转换到<void*>中,从而更好地对传递给PyCapsules的内容进行编译时类型检查。但在编译时不能解决问题:

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(或cffi)从任意Python可调用项创建C函数—请参见Using function pointers to methods of classes without the gil(答案底部)。这增加了一层额外的Python调用,因此速度不是很快,代码也有点混乱。ctypes通过使用运行时代码生成来实现这一点(这不是可移植的,也不是用纯C语言可以实现的)来动态构建一个函数,然后创建一个指向该函数的指针。在

尽管您在评论中声称您不认为可以使用Python解释器,但我不认为这是真的—Pythran生成Python扩展模块(因此非常绑定到Python解释器),并且它似乎可以在下面显示的测试用例中工作:

^{pr2}$

不幸的是,它只适用于cpdef,而不适用于cdef函数,因为它依赖于Python的可调用性。cdef函数可以与lambda一起使用(前提是将get_capi改为def,而不是{}):

'twice_cdef': make_PyCapsule(py_to_fptr(lambda x: self.twice_cdef(x)), b'int (int)'),

虽然有点乱,但可以让它工作。在

相关问题 更多 >