我想使用指针来改变python对象的值,这是我试过的:
这有效:
def main():
cdef int i
cdef int* iptr
i = 5
iptr = &i
iptr[0] = 1
print(i) # this works, prints 1
和此:
cdef class A:
cdef int i
def __init__(self, i): self.i = i
def main():
cdef int* ptr
cdef A a
a = A(5)
ptr = &a.i
ptr[0] = 1
print(a.i) # this works too
但是当我尝试对对象使用类似的代码时,我无法让它工作:
from cpython cimport PyObject
cdef class A:
cdef object o
def __init__(self, o): self.o = o
def main():
cdef PyObject* ptr
cdef A a
a = A(list())
# I also tried with `<void*>a.o`, `<PyObject*>a` and `<void*>a`
ptr = <PyObject*>a.o
# this segfaults
# ptr[0] = dict()
# print(a.o)
# this doesnt compile: Cannot convert Python object to 'PyObject'
# ptr[0] = None
# print(a.o)
# this doesnt compile: incompatible types when assigning to type ‘PyObject’ from type ‘struct PyObject *’
# ptr[0] = <PyObject>None
# print(a.o)
# this doesnt compile: Cannot assign type 'PyObject *' to 'PyObject'
# ptr[0] = <PyObject*>None
# print(a.o)
答案 0 :(得分:1)
如果您愿意将要更改的对象引用声明为PyObject *
而不是object
,则可以执行此操作。此外,为了使其工作,您必须自己管理对象的引用计数。这是你正在讨价还价 - 一旦你放弃了Python获取和设置变量的方式,你就处于C风格内存管理的土地上,一个错误会导致你泄漏内存,访问无效数据,删除你的对象在你完成它们之前,在你的记忆管理犯罪发生之后很久就会出现神秘的错误。因此,我真的建议不要走这条路线,而是使用标准的pythonic方式设置变量来引用对象。
但是,如果你真的必须这样,那么这段代码就像你希望你的例子一样工作。
from cpython cimport PyObject
from cpython.ref cimport Py_INCREF, Py_XDECREF
cdef class A:
cdef PyObject *o
def __init__(self, o):
cdef PyObject *tmp
tmp = self.o
Py_INCREF(o)
self.o = <PyObject *>o
Py_XDECREF(tmp)
def main():
cdef PyObject **ptr
cdef PyObject *tmp
cdef A a
a = A(list())
ptr = &a.o
new_obj = dict()
tmp = ptr[0]
Py_INCREF(new_obj)
ptr[0] = <PyObject *>new_obj
Py_XDECREF(tmp)
print <object>a.o
请注意,每当我们更改PyObject
引用时,我们首先递增新对象的引用,更改指针,然后对旧对象的引用进行decerement。我建议您始终遵循该模式 - 在递增之前递减可能会导致错误,如果您正在重置变量以引用同一对象,则可能会释放您仍在使用的对象。
此外,确保在操作Python对象时保持GIL,包括更新其引用计数。
除非您在C样式中声明对象引用,否则似乎没有办法说服Cython为您提供指向对象引用的指针。 (即PyObject *
而不是object
- 尽管在程序运行时这两件事情确实相同。区别仅在于编译时,Cython将对{{1}进行自动参考管理变量但不是object
变量。)
答案 1 :(得分:0)
好的,所以我决定在cythonized代码上挖一点,这就是我得到的:
cdef class A:
cdef object o
这将创建一个新的PyObject __pyx_obj_1p_A
和一个__pyx_tp_new_1p_A
类型(以及一个静态指针__pyx_ptype_1p_A
):
struct __pyx_obj_1p_A {
PyObject_HEAD
PyObject *o;
};
static PyObject *__pyx_tp_new_1p_A(PyTypeObject *t, CYTHON_UNUSED PyObject *a, CYTHON_UNUSED PyObject *k) {
struct __pyx_obj_1p_A *p;
PyObject *o;
if (likely((t->tp_flags & Py_TPFLAGS_IS_ABSTRACT) == 0)) {
o = (*t->tp_alloc)(t, 0);
}else{
o = (PyObject *) PyBaseObject_Type.tp_new(t, __pyx_empty_tuple, 0);
}
if (unlikely(!o)) {
return 0;
}
p = (struct __pyx_obj_1p_A *)o;
p->o = Py_None;
Py_INCREF(Py_None);
return o;
}
static PyTypeObject __pyx_type_1p_A = {
# members of the type struct
__pyx_tp_new_1p_A, # tp_new
# the rest of the members
}
一段代码,如:
from cpython cimport PyObject
from cython import address
cdef A a
cdef void* vptr
cdef PyObject* ptr
cdef PyObject** aptr
a = A()
ptr = <PyObject*>a.o
vptr = <void*>a.o
aptr = address(ptr)
aptr = &ptr
粗略对应于:
__pyx_v_a = (struct __pyx_obj_1p_A *) PyObject_Call((PyObject*)&__pyx_type_1p_A, args, NULL);
__Pyx_GOTREF(__pyx_v_a);
__pyx_v_ptr = ((PyObject *)__pyx_v_a->o);
__pyx_v_vptr = ((void *)__pyx_v_a->o);
__pyx_v_aptr = (&__pyx_v_ptr);
__pyx_v_aptr = (&__pyx_v_ptr);
所以,我的问题是cython强制使用临时变量,我得到堆栈上变量的地址,而不是结构属性,这是错误的。该程序可能会根据优化进行分段,对于我来说,`-O2&#39;该计划是分段的。