我需要在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的正确方法是什么?
答案 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()
但为了清楚起见省略了这些。在部分初始化失败后清理也是如此。