所以最近我做了一个项目,作为优化,我想使用numpy数组,而不是内置的python列表。它将是一个在两个轴上都具有固定长度的2d阵列。我还想最大限度地使用现金,这样代码就可以尽可能快。然而,当使用id(var(函数时,我发现了意想不到的结果:
代码:
a = numpy.ascontiguousarray([1,2,3,4,5,6,7,8,9], dtype=numpy.int32)
for var in a:
print(hex(id(var)))
返回:
x1aaba10d8f0
0x1aaba1f33d0
0x1aaba10d8f0
0x1aaba1f33d0
0x1aaba10d8f0
0x1aaba1f33d0
0x1aaba10d8f0
0x1aaba1f33d0
0x1aaba10d8f0
对我来说,这是一个超级奇怪的cus,意味着两个变量位于同一个内存块中(这是一件事吗?(。不管怎样,是我没有正确理解吗?
作为一个附带问题,构建二维阵列的原始任务能否用成本较低的方法来完成?Numpy数组附带了许多我不需要的函数。我只需要两样东西:
- 能够用[::-1]语法反转它
- 检查其中一个是否有效
提前感谢所有的帮助:-(
id(var)
并不像您想象的那样工作。实际上,id(var)
为指定的对象var
返回一个唯一的ID,但var
不是a
的单元格var
是引用a
单元格的Python对象。请注意,a
不包含这样的对象,因为它的效率太低(并且数据不会像请求的那样连续(。您看到重复ID的原因是以前的var
对象已被回收。
您真正想要的数组类型不清楚,目的也不清楚。但是,关于连续(或连续(和缓存的讨论表明,您不清楚Python是如何工作的。
首先,Python一直是面向对象的。整数、字符串、列表都是某个类的对象,具有关联的方法和属性。对于内置类,我们对存储没有什么发言权。
让我们列一个小清单:
In [89]: alist = [1,2,3,1000,1001,1000,'foobar']
In [90]: alist
Out[90]: [1, 2, 3, 1000, 1001, 1000, 'foobar']
列表有一个数据缓冲区,用于存储对内存中其他对象的引用(如果愿意,可以使用指针(。id
可能会给出一些位置的概念,它不应该被理解为c
语言意义上的"指针"。
对于此列表:
In [91]: [id(i) for i in alist]
Out[91]:
[9784896,
9784928,
9784960,
140300786887792,
140300786888080,
140300786887792,
140300786115632]
1,2,3的id值较小,因为Python在开始时初始化了小整数(最多256(。因此,所有用途都将具有唯一的id。
In [92]: id(2)
Out[92]: 9784928
在列表创建中,1000
似乎是唯一的,但在该上下文之外并不是唯一的。
In [93]: id(1001)
Out[93]: 140300786888592
看起来字符串也被缓存了,但这只是解释器的选择,我们不应该指望它
In [94]: id('foobar')
Out[94]: 140300786115632
反向列表是一个新列表,有自己的指针数组。但参考文献是一样的:
In [95]: rlist = alist[::-1]
In [96]: rlist
Out[96]: ['foobar', 1000, 1001, 1000, 3, 2, 1]
In [97]: rlist[5],id(rlist[5])
Out[97]: (2, 9784928)
像[::-1]
这样的索引操作应该只取决于列表中的项目数。它不取决于值实际指向的位置。其他副本也是如此。即使追加到数组也是相对独立于时间的(它在数据缓冲区中保持增长空间(。实际上,处理列表中的对象可能取决于它们在内存中的存储位置,但我们对此没有什么发言权。
A";2d";list实际上是一个包含列表元素的列表;嵌套列表。子列表存储在内存的其他位置,就像字符串和数字一样。从这个意义上说,嵌套列表是不连续的。
那么数组呢?
In [101]: x = np.arange(12)
In [102]: x
Out[102]: array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
In [104]: x.__array_interface__
Out[104]:
{'data': (57148880, False),
'strides': None, # default (8,)
'descr': [('', '<i8')],
'typestr': '<i8',
'shape': (12,),
'version': 3}
In [105]: x.nbytes # 12*8 bytes
Out[105]: 96
x
是一个ndarray
对象,具有shape
、strides
和dtype
等属性。还有一个数据缓冲区。在这种情况下是96字节长的c
阵列;57148880. We can't use that number, but I find it useful when comparing this
array_interfacedict across arrays. A
视图"尤其将具有相同或相关的值。
In [106]: x.reshape(3,4)
Out[106]:
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
In [107]: x.reshape(3,4).__array_interface__['data']
Out[107]: (57148880, False)
In [108]: x.reshape(3,4)[1,:].__array_interface__['data']
Out[108]: (57148912, False) # 32 bytes later
数组数据缓冲区具有实际值,而不是引用。这里,对于int
dtype,每8个字节被解释为"int64"值。
您的id
迭代实际上需要一个列表[x[i] for i in range(n)]
。数组的一个元素必须是"0";未装箱";,并且是类型为np.int64
的新对象。虽然不是数组,但它与1元素数组有很多共同的属性。
In [110]: x[4].__array_interface__
Out[110]:
{'data': (57106480, False),
...
'shape': (),....}
data
的值与x
的值无关。
只要在现有阵列上使用numpy
方法,速度就很好,通常是等效列表方法的10倍。但是,如果你从一个列表开始,那么制作一个数组需要时间。并且像列表一样处理数组是缓慢的。
x
的反面呢?
In [111]: x[::-1].__array_interface__
Out[111]:
{'data': (57148968, False),
'strides': (-8,),
'descr': [('', '<i8')],
'typestr': '<i8',
'shape': (12,),
'version': 3}
这是一个新的数组,但具有不同的strides
(-8,(,并且data
指向缓冲区的末尾880+96-8
。