将一个 numpy 数组附加到一个列表 - 奇怪的事情
Posted
技术标签:
【中文标题】将一个 numpy 数组附加到一个列表 - 奇怪的事情【英文标题】:Appending a numpy array to a list - strange happenings 【发布时间】:2019-05-18 08:59:20 【问题描述】:在 Raspberry Pi 上的 Raspbian 上使用 Spyder 3.1.3 中的 Python3.5.3。 将两个 numpy 数组附加到名为 'list0' 的列表中可以很好地与分配的 numpy 数组 'a' 类似:
import numpy as np
list0 = []
a = np.array([[1,2,3],[2,3,4]])
list0.append(a)
a = np.array([[11,12,13],[12,13,14]])
list0.append(a)
print("list0 =",list0)
效果很好,作为输出提供(帖子的格式更好):
list0 = [ array([[ 1, 2, 3], [ 2, 3, 4]]),
array([[11, 12, 13], [12, 13, 14]]) ]
使用循环将赋值替换为a,奇怪的事情发生了:
import numpy as np
a = np.empty((3), int)
list0 = []
for idx in range(4):
for i in range(3):
a[i] = idx*10 + i
print("idx =",idx,"; a =",a)
list0.append(a)
print("list0 =",list0)
第二行告诉 Python 使用的数组的形状(在我原来的例子中,它是一个三维数组)。为了验证生成的名为“a”的数组被打印出来。将新填充的数组 'a' 附加到 'list0' 最终显示最后一行的四倍。
idx = 0 ; a = [ 0 1 2]
idx = 1 ; a = [10 11 12]
idx = 2 ; a = [20 21 22]
idx = 3 ; a = [30 31 32]
list0 = [ array([30, 31, 32]), array([30, 31, 32]),
array([30, 31, 32]), array([30, 31, 32]) ]
我认为“list0”只包含四倍的指向数组“a”的指针,该数组仅存在于一个实例/内存范围中。
那么:如何将每个不同的数组 'a' 物理附加(复制?)到列表中?它是一个 python 错误还是只是我对某些东西的误解?当然,我应该多想pythonian ;c)
谢谢你的帮助,彼得
【问题讨论】:
这是将可变对象添加到列表时的常见问题。该列表存储指向对象的指针,在这种情况下,每个循环中的指针都是相同的。a
每次迭代都需要是一个新数组。您可以使用 a = idx*10 + np.arange(3)
保存迭代。
这是对 Python 语义的根本误解。阅读以下内容:nedbatchelder.com/text/names.html 在一种情况下,您正在创建两个不同的数组,并将它们附加到您的列表中,在另一种情况下,您创建一个数组,并将其两次附加到您的列表中,因此您的列表包含两个引用同一个数组。您可以使用.copy
方法复制数组。
@hpaulj 我认为最好不要考虑 Python 中的指针之类的东西(Python 中不存在),而是按照自己的术语学习语言。是的,确实如此,CPython 实现使用了一些原始的 PyObject 指针数组,但这是一个实现细节
@juanpa.arrivillaga,在 numpy 对象 dtype 数组的上下文中,我认为指针或引用是最好的术语。与ndarray
一样,数组的数据缓冲区存储itemsize
元素,无论它们引用(或指向)什么。该术语延续到我对lists
的讨论中。
没错,但列表不是指针列表。指针实际上与 python 无关。
【参考方案1】:
问题
您将相同的数组 a
附加到您的 list0
4 次。像a
这样的数组是可变对象,这意味着当你给它们赋值时,底层对象会发生变化。由于该数组在您的列表中出现了 4 次,因此这些更改(似乎)出现在 4 个不同的地方。
解决方案
您只需稍作改动即可修复您拥有的代码。将数组的副本附加到列表中,而不是数组本身:
import numpy as np
a = np.empty((3), int)
list0 = []
for idx in range(4):
for i in range(3):
a[i] = idx*10 + i
print("idx =",idx,"; a =",a)
list0.append(a.copy())
print("list0 =",list0)
输出:
idx = 0 ; a = [0 1 2]
idx = 1 ; a = [10 11 12]
idx = 2 ; a = [20 21 22]
idx = 3 ; a = [30 31 32]
list0 = [array([0, 1, 2]), array([10, 11, 12]), array([20, 21, 22]), array([30, 31, 32])]
优化方案
Python/Numpy 提供了许多更好的方法来初始化数组(无论是使用更少的代码行还是运行速度更快)。对于像这样的一堆范围,这是一个合理的方法:
list0 = [np.arange(n*10, n*10+3) for n in range(4)]
print(list0)
输出:
[array([0, 1, 2]), array([10, 11, 12]), array([20, 21, 22]), array([30, 31, 32])]
您也可以考虑只使用单个二维数组来代替数组列表。单个数组通常比列表中的异构数组更易于使用。这样做的方法如下:
arr0 = np.array([np.arange(n*10, n*10+3) for n in range(4)])
print(arr0)
输出:
[[ 0 1 2]
[10 11 12]
[20 21 22]
[30 31 32]]
【讨论】:
非常感谢电话(和其他人)。对理解和从我的 pymind 中提出指针有很大帮助。 .copy() 立即工作正常。我的数据不是像示例中那样生成,而是从 pyserial 以二进制数组接收并分发到 3D 矩阵。这些被堆叠在一起以供以后评估。期待将其调整为更 numpy 和更快的方法。【参考方案2】:这样做:
list_to_append.append(np_array.copy())
简而言之,numpy 数组或列表是可变对象,这意味着当您将 numpy 数组或列表分配给变量时,您真正分配的是什么是对内存位置的引用,也就是指针。
在您的情况下,“a”是一个指针,因此您真正要做的是将地址附加到 list0 到“a”指向的内存位置,而不是指针指向的实际值。 因此,这意味着“list0”的每个新位置,在附加之后,结果都是相同的内存地址:“a”。
所以,而不是:
list0.append(a)
你调用 "a" 的 copy() 方法为 "a" 的新值创建一个新的内存位置并返回它:
list0.append(a.copy())
【讨论】:
如果你能解释为什么 OP 的方法没有像他们预期的那样表现会很好,因为这也将帮助未来的读者解决这个问题。然后,您可以解释为什么您的方法没有遇到此类问题。 虽然此代码可以解决问题,including an explanation 说明如何以及为什么解决问题将真正有助于提高您的帖子质量,并可能导致更多的赞成票。请记住,您正在为将来的读者回答问题,而不仅仅是现在提问的人。请edit您的回答添加解释并说明适用的限制和假设。 关于上述答案的要求解释:您的建议对我也很有帮助,如果我能回答问题,请不要忘记一些解释。上面贴了很好的解释;对我更深入的理解和进一步阅读有价值的想法。但是 Fsn9 的回答会在一秒钟内帮助我,认为指针不是 Python 中的思考方式;c) 感谢您的建议。我想现在好多了。以上是关于将一个 numpy 数组附加到一个列表 - 奇怪的事情的主要内容,如果未能解决你的问题,请参考以下文章