numpy数组值和对象属性都可能指向内存中的相同位置吗?

问题描述

在我正在开发的项目中,我想知道是否可以做类似的事情:

class P:
    def __init__(self,x):
        self.x = x

    def __str__(self):
        return str(self.x)

    def __repr__(self):
        return self.__str__()

obj_lst = [P(x=2),P(x=3),P(x=4),P(x=5)]

np_x = array([p.x for p in obj_lst])

obj_lst[0].x = 10
print(np_x)

预期结果将是

array([10,3,4,5])

np_x[2] = 20
print(obj_lst)

我会得到的,

[10,20,5]

因此,对象的属性和数组中的值都指向内存中的同一位置。

这样,我就可以将OOP抽象和复杂运算的速度用在numpy速度上。

解决方法

如果您在框外进行了一些思考(可能会稍微调整一下要求),则可以这样做。让我们颠倒设置过程的方式,并创建一个首先保存数据的缓冲区:

np_x = np.array([2,3,4,5])

现在,以不同的方式定义您的班级。除了记录x value 之外,我们将指向它的指针记录为数组和索引(以后您可以对原始内存位置进行一些有趣的操作,但是对于现在)。您可以通过在类中将x属性设置为property并将其数据存储在同名的instance属性中,从而保持几乎完全相同的接口:

class P:
    def __init__(self,buffer,offset):
        self.__dict__['x'] = (buffer,offset)

    @property
    def x(self):
        buf,off = self.__dict__['x']
        return buf[off]

    @x.setter
    def x(self,value):
        buf,off = self.__dict__['x']
        buf[off] = value

    def __str__(self):
        return str(self.x)

    def __repr__(self):
        return self.__str__()

现在,您可以列出对象了。这是代码中唯一在类定义之外更改的部分:

obj_lst = [P(np_x,0),P(np_x,1),2),3)]

您的所有更改现在都是透明的,因为您共享一个缓冲区:

>>> obj_lst[0].x = 10
>>> np_x
array([10,5])
>>> np_x[-2] = 20
>>> obj_lst
[10,20,5]

与此相关的一件整洁的事情是,P实际上可以与支持__getitem____setitem__的任何类型一起使用,而不管其如何编制索引。例如,您可以将其应用于dict

>>> d_x = {'a': 2,'b': 3,'c': 4,'d': 5}
>>> obj_lst = [P(d_x,'a'),P(d_x,'b'),'c'),'d')]
>>> obj_lst[0].x = 10
>>> d_x
{'a': 10,'c': 20,'d': 5}
>>> d_x['c'] = 20
>>> obj_lst
[10,5]

您还可以为numpy数组提供复杂的索引:

>>> np_x = np.arange(10)
>>> obj_lst = [P(np_x,slice(1,None,2)),[1,2,6,8])]
>>> obj_lst
[0,[1 3 5 7 9],[1 2 6 8]]
>>> obj_lst[-1].x = 100
>>> np_x
array([  0,100,5,7,9])
>>> np_x[5:] = 20
>>> obj_lst
[0,[100   3  20  20  20],[100 100  20  20]]