在Cython中使用PyCapsule

时间:2017-10-01 08:38:40

标签: c python-3.x cython

摘要

我需要在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
  • Cython:0.26
  • 操作系统:Windows 10(64位)
  • Python:3.5.3
  • Spyder(IDE):3.2.3

2 个答案:

答案 0 :(得分: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

答案 1 :(得分:1)

按照DavidW的回答(双关语意图)的指示,我继续修改我的代码,使其完全适用于C结构和一些错误处理。我使用了malloc的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()

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