如何将PyObject *指针设置为None?

时间:2015-03-02 02:35:58

标签: python pointers cython

我想使用指针来改变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)

2 个答案:

答案 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;该计划是分段的。