将一个 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 数组附加到一个列表 - 奇怪的事情的主要内容,如果未能解决你的问题,请参考以下文章

Python - 如何创建一个空的numpy数组并附加到它,如列表[重复]

在 Numpy Python 中将一维数组附加到二维数组

Numpy 数组和列表的多重赋值,一个奇怪的例子

将数组列表作为列附加到具有相同列索引的熊猫数据框中

Numpy - 将行添加到数组

Numpy 将矩阵附加到张量