【问题标题】:Using PyCapsule in Cython在 Cython 中使用 PyCapsule
【发布时间】:2022-03-01 23:50:04
【问题描述】:

总结

我需要在 Python 对象中存储一个 C 结构,以便在使用 Cython 增强的其他部分中使用。我相信 PyCapsule 最适合这个目的,但我的代码结果不是我所期望的。虽然指针地址正确返回,但内存似乎已被释放。

详情

我是 Cython 的新手,我正在学习使用它来加速我的部分代码。为了提出这个问题,我简化了我的代码,并使用了 int 来代替 struct。

我根据对PyCapsule documentation的理解编写了CythonTest.pyx,并使用标准命令用setup.py编译:

python setup.py build_ext --inplace

CythonTest.pyx

#cython: language_level=3

from cpython.pycapsule cimport PyCapsule_New, PyCapsule_IsValid, PyCapsule_GetPointer

class Test:
    def __init__(self):
        cdef int test = 10
        cdef const char *name = "test"
        self.vars = PyCapsule_New(<void *>&test, name, NULL)
        
        # Print pointer address
        print("{0:x}".format(<unsigned long long>test))
        
    def peek(self):
        cdef const char *name = "test"
        if not PyCapsule_IsValid(self.vars, name):
            raise ValueError("invalid pointer to parameters")
        cdef int *test = <int *>PyCapsule_GetPointer(self.vars, name)
        print(test[0])
        
        # Print pointer address
        print("{0:x}".format(<unsigned long long>test))

setup.py

from distutils.core import setup
from Cython.Build import cythonize

setup(ext_modules=cythonize("CythonTest.pyx"))

然后,我使用以下 Python 脚本运行它。

from CythonTest import Test

test = Test()
print(test.vars)
test.peek()

控制台打印出以下内容:

cbde7ebe70
<capsule object "test" at 0x0000027516467930>
0
cbde7ebe70

似乎指针已成功存储在 PyCapsule 中,并按照相同地址的指示进行检索。但是,地址中现在存储的是 0 而不是 10。我知道使用 int 可能会导致它被垃圾收集并改变了问题的性质,但是在使用 PyMem_Malloc 时也会出现同样的问题。

那么问题来了:PyCapsule的正确使用方法是什么?

环境

  • 编译器:Visual Studio Express 2015
  • 赛通:0.26
  • 操作系统:Windows 10(64 位)
  • Python:3.5.3
  • Spyder (IDE):3.2.3

【问题讨论】:

    标签: c python-3.x cython


    【解决方案1】:

    test 是一个局部变量(在 C 中),因此在 __init__ 函数的末尾不存在,因此当您尝试在 peek 中再次访问它时,内存已被用于其他用途.

    您可以改为为堆的test 分配内存,以便变量在您需要时持续存在(尽管您需要创建一个析构函数来释放它)。

    from libc.stdlib cimport malloc, free
    
    # destructor
    cdef void free_ptr(object cap):
       # This should probably have some error checking in
       # or at very least clear any errors raised once it's done
       free(PyCapsule_GetPointer(cap,PyCapsule_GetName(cap)))
    
    class Test:
        def __init__(self):
           cdef int* test = malloc(sizeof(int)) 
           test[0] = 10
           cdef const char *name = "test"
           self.vars = PyCapsule_New(<void *>test, name, &free_ptr)
    
           # etc
    

    【讨论】:

    • 恐怕我目前无法对此进行测试。我很确定我已正确识别问题,但不能保证替换代码有效。
    • 感谢您的回答,为结构(而不​​仅仅是其成员)分配内存也解决了问题。你能解释一下需要什么样的错误检查吗?我使用的 PyCapsule_IsValid 是否足够?作为旁注,我应该发布我的完整工作解决方案吗?如果是这样,它应该附加到问题中还是作为答案?
    • PyCapsule_IsValid 绝对是个好主意。您可能还想测试您调用的任何PyCapsule_* 函数都不会引发错误。没有办法从析构函数返回错误(因为它返回 void),所以您可能只想打印并清除它:docs.python.org/3/c-api/exceptions.html#printing-and-clearing
    • 如果您认为您的完整工作解决方案对其他人有用,请随时发布。如果您确实决定发布,则应将其作为答案发布。
    【解决方案2】:

    按照 DavidW 的回答(双关语 intent)中的指示,我继续修改我的代码,以使其完全可以使用 C 结构和一些错误处理。我在这里使用了 malloc 和 free 的 PyMem 版本,因为据说它们工作得更好。

    #cython: language_level=3
    
    from cpython.exc cimport PyErr_Occurred, PyErr_Print
    from cpython.mem cimport PyMem_Malloc, PyMem_Free
    from cpython.pycapsule cimport *
    
    cdef struct params:
        double *param1
    
    class Test:
        def __init__(self):
            cdef int index
            cdef params *test = <params *>PyMem_Malloc(sizeof(params))
            test.param1 = <double *>PyMem_Malloc(sizeof(double))
            test.param1[0] = 0.5
            cdef const char *name = "test"
            self.vars = PyCapsule_New(<void *>test, name, NULL)
            print(test.param1[0])
    
        def peek(self):
            cdef const char *name = "test"
            if not PyCapsule_IsValid(self.vars, name):
                raise ValueError("invalid pointer to parameters")
            cdef params *test = <params *>PyCapsule_GetPointer(self.vars, name)
            print(test.param1[0])
    
        def __del__(self):
            cdef const char *name = "test"
            cdef params *pointer = <params *>PyCapsule_GetPointer(self.vars, name)
            if PyErr_Occurred():
                PyErr_Print()
            else:
                PyMem_Free(test.param1)
                PyMem_Free(test)
    

    像往常一样,基本检查应该在内存分配中以以下形式完成:

    if not pointer:
        raise MemoryError()
    

    但为了清楚起见,这些都被省略了。部分初始化失败后的清理也是如此。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-01-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-12-09
      • 1970-01-01
      • 2014-07-15
      • 2018-08-25
      相关资源
      最近更新 更多