Python类中的Numpy矩阵表现出链接行为?

3
如果你在Python中这样定义一个类:
 import numpy as np
 class Foo:

    def __init__(self, data):
        self.data = data 
        self.data_copy = self.copy(self.data)

    def copy(self, data):
        a = []
        for e in data:
            a.append(e)
        return a

    def change(self, val):
        for i in range(0, len(self.data_copy)):
            self.data_copy[i] += val

然后创建一个类的实例,例如:

a = Foo([np.matrix([1,2,3]), np.matrix([5,6,7])])

现在,如果我们调用 a.change(np.matrix([5,5,2]))函数,它只应该修改self.data_copy列表,但self.data列表也会随着更改而更新。看起来即使创建了一个新列表,两个列表中的Numpy矩阵仍然保持连接。这在某些方面是一个不错的特性,但如果我传递了一个普通的Python数字列表,则无法正常工作。这是一个bug吗,还是Numpy矩阵复制的副作用?如果是后者,那么用普通的Python列表复制此行为的最佳方法是什么?

你会看到 a = Foo([[1], [2]])a.change([3]) 有相同的行为。 - Eric
但是这种行为在类外部不会发生,那么为什么在类内部会发生呢?此外,如果将change()函数更改为=而不是+=,则该行为将消失... - rugrln
我稍微误解了你的问题。我的答案已经更新。 - spruceb
1个回答

4
当你进行“拷贝”操作时,你只是创建了一个包含与旧列表相同对象的新列表。也就是说,当你遍历时,你实际上是在遍历其中的对象引用,当你添加时,你添加的是一个引用而不是新对象或副本。因此,对这些对象所做的任何修改都将反映在任何引用它们的其他列表中。看起来你想要的是实际矩阵的副本。为了实现这一点,在你的方法中,不要追加,而是追加类似于的东西。这将创建矩阵的真正副本,而不仅仅是指向旧矩阵的新引用。
更普遍地说,Python对象实际上总是按引用传递的。这对于不可变对象(例如字符串、整数、元组等)没有影响,但对于可以改变的列表、字典或用户定义的类,你需要做出明确的复制。通常使用内置的模块或直接从旧对象构造一个新对象是你想要做的。
编辑: 我现在明白了你的意思。我稍微误解了你最初的问题。你指的是<+=>突变矩阵对象,而不是真正的。这仅仅是大多数Python集合类型中<+=>的工作方式。实际上,<+=>是一个单独的方法,不同于相加后的赋值结果。对于普通的Python列表,你仍然会看到这种行为。
a = [1, 2, 3]
b = a
b += [4]
>>> print(a)
[1, 2, 3, 4]

你可以看到,+= 是改变原始对象而不是创建一个新的对象并将 b 引用它。但是如果你这样做:
b = b + [4]
>>> print(a)
[1, 2, 3]
>>> print(b)
[1, 2, 3, 4]

这将具有期望的行为。对于集合(列表、numpy数组),+ 运算符确实会返回一个新对象,但是 += 通常只是改变旧对象。

我明白了,现在很有意义!不过有趣的是,如果你不小心就会出错!谢谢! - rugrln

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接