cython 中内存视图的地址相同但指向不同的对象

Address of memoryviews in cython are the same but point to different object

问题

在 cython 中定义不同的对象时,内存视图将 return 相同的地址。但是,数组本身在索引时会被修改。

背景。

我有用 cython 编写的基础 class 和派生 class。我注意到,当我将多处理应用到 classes 时,底层缓冲区在不同的进程中发生了变化,这不是故意的。在酸洗过程中,我编写了一个简单的 __reduce__ 方法和 __deepcopy__ 方法来重建原始对象。为了清楚起见,我降低了下面代码的复杂性。现在我的问题是,为什么 memoryviews return 的地址相同?此外,为什么即使内存视图相同,numpy 数组本身也会正确更改

#distutils: language=c++
import numpy as np
cimport numpy as np
cdef class Temp:
    cdef double[::1] inp
    def __init__(self, inp):
        print(f'id of inp = {id(inp)}')
        self.inp = inp

cdef np.ndarray x = np.ones(10)
cdef Temp a       = Temp(x)
cdef Temp b       = Temp(x)
cdef Temp c       = Temp(x.copy())
b.inp[0] = -1
c.inp[2] = 10
print(f'id of a.inp = {id(a.inp)}\nid of b.inp = {id(b.inp))}\nid of c.inp = {id(c.inp)}')
print(f'id of a.inp.base = {id(a.inp.base)}\nid of b.inp.base = {id(b.inp.base))}\nid of c.inp.base = {id(c.inp.base)}')

print('a.inp.base',a.inp.base)
print('b.inp.base',b.inp.base) # expected to be the same as a
print('c.inp.base',c.inp.base) # expected to be different to a/b

输出:

id of inp = 139662709551872
id of inp = 139662709551872
id of inp = 139662709551952
id of a.inp = 139662450248672
id of b.inp = 139662450248672
id of c.inp = 139662450248672
id of a.inp.base = 139662709551872
id of b.inp.base = 139662709551872
id of c.inp.base = 139662709551952
a.inp.base [-1.  1.  1.  1.  1.  1.  1.  1.  1.  1.]
b.inp.base [-1.  1.  1.  1.  1.  1.  1.  1.  1.  1.]
c.inp.base [ 1.  1. 10.  1.  1.  1.  1.  1.  1.  1.]

我们所说的类型化内存视图不是一个单一的 class:根据上下文(Cython 代码,纯 Python 代码)它会在后台更改其身份。

那么让我们从

开始
%%cython 
cdef class Temp:
    cdef double[::1] inp

此处 double[::1] inp 的类型 __Pyx_memviewslice 不是 Python 对象:

typedef struct {
  struct {{memview_struct_name}} *memview;
  char *data;
  Py_ssize_t shape[{{max_dims}}];
  Py_ssize_t strides[{{max_dims}}];
  Py_ssize_t suboffsets[{{max_dims}}];
} {{memviewslice_name}};

当我们调用 id(self.inp) 时会发生什么?显然,id 是一个纯 Python 函数,因此必须从 self.inp 创建一个新的临时 python-对象(一个内存视图)(只是为了能够调用 id) 然后直接销毁。临时 Python 对象的创建是通过 __pyx_memoryview_fromslice.

完成的

知道了,很容易解释,为什么 ids 相等:尽管是不同的对象,临时内存视图巧合地具有相同的地址(因此相同 id,这是 C 的一个实现细节Python), 因为内存被CPython.

一遍又一遍的重复使用

类似的场景在Python中到处都是,这里是an example for method-objects,或者更简单的:

class A:
    pass
# the life times of temporary objects don't overlap, so the ids can be the equal
id(A())==id(A())
# output: True

# the life times of objects overlap, so the id cannot be equal 
a,b=A(), A()
id(a)==id(b)
# output: False

简而言之:您的期望是,相同的 id 表示相同的对象是错误的。只有当对象的生命周期重叠时,这个假设才成立。