2

So I have a 4 by 3 nested list (4 rows, 3 columns) that I have nested as follows:

>>> c= [x[:] for x in [[None]*3]*4]
>>> print c
[[None, None, None], 
 [None, None, None], 
 [None, None, None], 
 [None, None, None]]

I have initialized my nested list in this fashion because this other SO question does a good job of explaining why some other methods don't work. (like c = [[None]*3]*4)

Now I want to update all elements in the first row to 0. i.e. I want to set all elements in

c[0] to 0. So I tried the following:
>>> for x in c[0]: x = 0
...
>>> c
[[None, None, None], [None, None, None], [None, None, None], [None, None, None]]
>>>

As you can see, the elements were not updated. The following worked however:

>>> c[0] = [0 for x in c[0]]
>>>
>>> c
[[0, 0, 0], [None, None, None], [None, None, None], [None, None, None]]

And I was almost sure it would because I'm creating a new list of 0s and assigning it to c[0].

Anyway, I then went on to use the for loop and tried to update the first column (i.e. the first element of every row) to 0 and that worked.

>>> for x in c: x[0] = 0
...
>>> c
[[0, None, None], [0, None, None], [0, None, None], [0, None, None]]

I understand that this for loop update is different from the previous for loop update since the first one tried to loop over single elements while this one loops over lists and just accesses the first element of each list.

I'm sure I'm missing something about names pointing to other names but I can't put my finger on what the exact issue is here. Could someone please help?

4

3 回答 3

3
for x in c[0]: x = 0

在这个循环中,您实际上是在创建对该列表中存在的整数的新引用,然后更改这些新引用。

由于整数是不可变的,因此原始引用不会受到影响。另外,由于赋值不是就地操作,所以这也不会影响可变对象。

>>> a = b = 1
>>> b += 1     # in-place operation, this will work differently for mutable objects
>>> a, b       # a is still unchanged
(1, 2)

赋值操作也不会影响可变对象:

>>> x = y = [1, 1]
>>> x = 2 # `x` now points to a new object, number of references to [1, 1] decreased by 1
>>> x, y
(2, [1, 1])

但就地操作将:

>>> x = y = [1, 1]
>>> x.append(2)
>>> x, y
([1, 1, 2], [1, 1, 2])

所以,上面的循环实际上相当于:

x = c[0]
x = 0    #this won't affect `c[0]`
x = c[1]
x = 0
...

在这个循环中,您实际上将 c[0] 更改为指向一个新的列表对象:

>>> c= [x[:] for x in [[None]*3]*4]
>>> c= [x[:] for x in [[None]*3]*4]
>>> print id(c[0])
45488072
>>> c[0] = [0 for x in c[0]]
>>> print id(c[0])              #c[0] is a new list object
45495944
于 2013-10-29T06:23:20.233 回答
1

在每种情况下,x都是对某物的引用。

在它是对 的引用的情况下None,您正在创建一个实例int(0)并切换x到引用它。所以这里c根本不涉及。

在其他情况下x是对 的组件的引用c,因此当您修改该组件时,您会看到更改反映在c

于 2013-10-29T06:24:28.007 回答
1

现在我想将第一行中的所有元素更新为 0。即我想将所有元素设置c[0]为 0。所以我尝试了以下操作:

>>> for x in c[0]: x = 0

>>> c
[[None, None, None], [None, None, None], [None, None, None], [None, None, None]]
>>>

如您所见,元素没有更新。

那是因为c[0] 中x的每个None值依次被赋予,所以你实际上是在说:

x = None
x = 0
x = None
x = 0
x = None
x = 0

这没有任何用处。

然而,以下工作:

>>> c[0] = [0 for x in c[0]]
>>>
>>> c
[[0, 0, 0], [None, None, None], [None, None, None], [None, None, None]]

我几乎可以肯定它会,因为我正在创建一个新的 0 列表并将其分配给 c[0]。

这是有效的,因为c[0]它是对...中第一个元素(列表)的引用或别名,c您可以为其分配一个新值,它会影响原始c容器。这里的关键是直写参考/别名的想法。我不确定 python 社区倾向于称之为什么(我主要是 C++ 程序员),但从功能上讲,您c[0]引用了该元素c并允许您覆盖它。 c[0]不像x上面那样,x有效地看到了元素值 () 的不可变副本None,但是该值与容器没有任何联系或写回容器的能力c。那是因为None是一个简单的“标量”值。

这种认为变量有时有效地引用分配给它们的值的不可变副本的想法,进一步的分配将解除它们的关联,而其他时候实际上继续引用分配的值本身,从而进一步写入允许对其进行修改,实际上很常见到几种解释语言(例如 Ruby 也这样做)。一开始很迷惑!这些语言希望你认为它们比 C++ 更简单,因为这些“引用”是明确的并且有自己的语法/符号,但实际上你很快就会被咬,无论如何都必须学会理解差异,然后开始使用copy.deepcopy当您需要真正独立的数据副本时。他们希望看起来很直观,但是直观的东西——深度复制值——对于大型容器和对象来说太昂贵了:大多数新手的程序会变得非常慢,他们不知道为什么。因此,现代脚本语言倾向于采用这种危险的行为,即让您写入您可能认为已经安全地复制了一些数据的东西。在使用该语言几周后,它可能都点击了......虽然最初令人困惑,但它实际上也很有用 - 必须使用原始变量名称、所有键和索引来引用数据以将事情缩小到特定标量值可能非常冗长,当然这是允许函数对部件进行操作的必要抽象复杂的对象/容器它们被传递而不必知道对象的其余部分......

无论如何,然后我继续使用 for 循环并尝试将第一列(即每行的第一个元素)更新为 0 并且有效。

>>> for x in c: x[0] = 0
...
>>> c
[[0, None, None], [0, None, None], [0, None, None], [0, None, None]]

这是有效的,因为x依次作为每个列表的引用/别名绑定,并且由于列表是“复杂”对象,因此引用/别名允许我上面描述的直写能力。

我知道这个 for 循环更新与之前的 for 循环更新不同,因为第一个循环更新尝试循环单个元素,而这个循环循环遍历列表并仅访问每个列表的第一个元素。

于 2013-10-29T06:32:45.390 回答