在类级跟踪Python 2.7.x对象属性以快速构造numpy数组

时间:2017-04-20 14:46:31

标签: python numpy

假设我们有一个类的实例列表,它们都有一个我们知道是浮点的属性 - 调用属性x。在程序的各个点上,我们想要提取x的所有值的numpy数组,以便对x的分布进行一些分析。这个提取过程已经完成了很多,并且已被确定为程序的一个缓慢部分。这是一个非常简单的例子来具体说明我的想法:

import numpy as np

# Create example object with list of values
class stub_object(object):
    def __init__(self, x):
        self.x = x

# Define a list of these fake objects
stubs = [stub_object(i) for i in range(10)]

# ...much later, want to quickly extract a vector of this particular attribute:
numpy_x_array = np.array([a_stub.x for a_stub in stubs])

这里有一个问题:是否有一种更聪明,更快速的方式来跟踪" x"在"存根"中跨越stub_object实例的属性列表,这样构建" numpy_x_array"比上面的过程更快?

这里有一个粗略的想法我想要敲定:我可以创建一个"全局到班级类型" numpy向量,随着对象集的更新而更新,但我可以在任何时候有效地运行吗?

我真正想要的只是朝着正确的方向努力。"提供关键字我可以谷歌/搜索SO / docs正是我正在寻找的。

对于它的价值,我已经研究了这些,这让我更进一步,但并不是完全在那里:

我看过的其他人,但没有那么有用:

(当然,一个选项是"简单地"彻底检查代码的结构,这样就不会使用"存根"" stub_objects的列表,&# 34;有一个大对象,比如stub_population,它维护列表和/或numpy数组中的相关属性,以及简单地作用于那些数组的元素的方法。其缺点是大量的重构,以及一些减少抽象和灵活性建模" stub_object"作为它自己的东西。如果有一个聪明的方法,我想避免这种情况。)

修改:我使用的是2.7.x

编辑2: @hpaulj,您的示例得到了很大的帮助 - 接受了答案。

这是上面示例代码的非常简单的第一遍版本,它正在做我想要的。有一个非常简单的指示可能是一个数量级的加速,没有显着重新排列代码体。 很好。谢谢!

size = 20

# Create example object with list of values
class stub_object(object):
    _x = np.zeros(size, dtype=np.float64)

    def __init__(self, x, i):
        # A quick cop-out for expanding the array:
        if i >= len(self._x):
            raise Exception, "Index i = " +str(i)+ " is larger than allowable object size of len(self._x) = "+ str(self._x)
        self.x = self._x[i:i+1]
        self.set_x(x)

    def get_x(self):
        return self.x[0]

    def set_x(self, x_new):
        self.x[0] = x_new

# Examine:

# Define a list of these fake objects
stubs = [stub_object(x=i**2, i) for i in range(size)]

# ...much later, want to quickly extract a vector of this particular attribute:
#numpy_x_array = np.array([a_stub.x for a_stub in stubs])

# Now can do: 
numpy_x_array = stub_object._x  # or
numpy_x_array = stubs[0]._x     # if need to use the list to access

还没有使用属性,但真的非常喜欢这个想法,并且它应该在使代码非常接近未改变方面走得很远。

1 个答案:

答案 0 :(得分:3)

基本问题是你的对象是通过内存存储的,每个对象的字典中都有属性。但是对于数组工作,值必须存储在连续的数据缓冲区中。

我已经在其他SO问题中探讨了这个问题,但是你发现的问题更早。我还有很多东西需要补充。

np.array([a_stub.x for a_stub in stubs])

使用itertoolsfromiter的备选方案不应该更改速度,因为时间消费者是a_stub.x访问权限,而不是迭代机制。您可以通过测试

之类的更简单的方法来验证
np.array([1 for _ in range(len(stubs))]

我怀疑最好的选择是使用一个或多个数组作为主存储,并重构您的类,以便从该存储中获取该属性。

如果您知道您将拥有10个对象,那么请创建一个该大小的空数组。创建对象时,为其指定唯一索引。 x属性可以是property,其getter / setter访问该数组的data[i]元素。通过使x成为属性而不是主属性,您应该能够保留大部分对象机制。您只需更改几种方法即可尝试不同的存储方法。

我试图使用类属性作为主要数组存储来绘制它,但我仍然有一些错误。

具有访问数组的x属性的类:

class MyObj(object):
    xdata = np.zeros(10)
    def __init__(self,idx, x):
        self._idx = idx
        self.set_x(x)
    def set_x(self,x):
        self.xdata[self._idx] = x
    def get_x(self):
        return self.xdata[self._idx]
    def __repr__(self):
        return "<obj>x=%s"%self.get_x()    
    x = property(get_x, set_x)

In [67]: objs = [MyObj(i, 3*i) for i in range(10)]
In [68]: objs
Out[68]: 
[<obj>x=0.0,
 <obj>x=3.0,
 <obj>x=6.0,
 ...
 <obj>x=27.0]
In [69]: objs[3].x
Out[69]: 9.0
In [70]: objs[3].xdata
Out[70]: array([  0.,   3.,   6.,   9.,  12.,  15.,  18.,  21.,  24.,  27.])
In [71]: objs[3].xdata += 3
In [72]: [o.x for o in objs]
Out[72]: [3.0, 6.0, 9.0, 12.0, 15.0, 18.0, 21.0, 24.0, 27.0, 30.0]

在适当的位置更改数组是最简单的。但也可以替换阵列本身(从而“增长”类集)

In [79]: MyObj.xdata=np.ones((20,))    
In [80]: a = MyObj(11,25)
In [81]: a
Out[81]: <obj>x=25.0
In [82]: MyObj.xdata
Out[82]: 
array([  1.,   1.,   1.,   1.,   1.,   1.,   1.,   1.,   1.,   1.,   1.,
        25.,   1.,   1.,   1.,   1.,   1.,   1.,   1.,   1.])
In [83]: [o.x for o in objs]
Out[83]: [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]

我们必须小心修改属性。例如,我试过

objs[3].xdata += 3

打算为全班改变xdata。但最终只为该对象分配了一个新的xdata数组。我们还应该能够自动增加对象索引(这些天我比numpy方法更熟悉Python类结构。)

如果我将getter替换为取一个切片的那个:

 def get_x(self):
     return self.xdata[self._idx:self._idx+1]

In [107]: objs=[MyObj(i,i*3) for i in range(10)]
In [109]: objs
Out[109]: 
[<obj>x=[ 0.],
 <obj>x=[ 3.],
 ...
 <obj>x=[ 27.]]

np.info(或.__array_interface__)向我提供有关xdata数组的信息,包括其数据缓冲区指针:

In [110]: np.info(MyObj.xdata)
class:  ndarray
shape:  (10,)
strides:  (8,)
itemsize:  8
aligned:  True
contiguous:  True
fortran:  True
data pointer: 0xabf0a70
byteorder:  little
byteswap:  False
type: float64

第一个对象的切片指向同一个地方:

In [111]: np.info(objs[0].x)
class:  ndarray
shape:  (1,)
strides:  (8,)
itemsize:  8
....
data pointer: 0xabf0a70
...

下一个对象指向下一个float(进一步8个字节):

In [112]: np.info(objs[1].x)
class:  ndarray
shape:  (1,)
...
data pointer: 0xabf0a78
....

我不确定切片/视图的访问是否值得。