为什么原来的名单变了?

Why does the original list change?

运行这个:

a = [[1], [2]]
for i in a:
    i *= 2
print(a)

给予

[[1, 1], [2, 2]]

我希望得到原始列表,就像这里发生的那样:

a = [1, 2]
for i in a:
    i *= 2
print(a)

给出:

[1, 2]

为什么修改了第一个例子中的列表?

在第一种情况下,for 循环中的 ilist。所以你告诉 python 嘿,拿 ith 列表并重复两次。您基本上是在重复列表 2 次,这就是 * 运算符对列表所做的。
在第二种情况下,您的 i 是一个值,因此您正在应用 * 到一个值,而不是列表。

您正在使用 augmented assignment statements。这些对左侧命名的对象进行操作,使该对象有机会更新 就地:

An augmented assignment expression like x += 1 can be rewritten as x = x + 1 to achieve a similar, but not exactly equal effect. In the augmented version, x is only evaluated once. Also, when possible, the actual operation is performed in-place, meaning that rather than creating a new object and assigning that to the target, the old object is modified instead.

(大胆强调我的)。

这是通过让对象实现 __i[op]__ 方法来实现的,因为 =*__imul__ hook:

These methods are called to implement the augmented arithmetic assignments (+=, -=, *=, @=, /=, //=, %=, **=, <<=, >>=, &=, ^=, |=). These methods should attempt to do the operation in-place (modifying self) and return the result (which could be, but does not have to be, self).

在列表上使用 *= 该列表对象 和 return 相乘为相同的列表对象 (self) 'assigned'回同名.

另一方面,整数是不可变的对象。对整数的算术运算 return new 整数对象,所以 int 对象甚至没有实现 __imul__ 钩子;在这种情况下,Python 必须回退到执行 i = i * 3

所以对于第一个例子,代码:

a = [[1], [2]]
for i in a:
    i *= 2

真的这样做了(出于说明目的展开了循环):

a = [[1], [2]]
i = a[0].__imul__(2)  # a[0] is altered in-place
i = a[1].__imul__(2)  # a[1] is altered in-place

其中 list.__imul__ 方法将更改应用于列表对象本身, return 是对列表对象的引用。

对于整数,改为执行:

a = [1, 2]
i = a[0] * 2  # a[0] is not affected
i = a[1] * 2  # a[1] is not affected

所以现在 new 整数对象被分配给 i,它独立于 a.

每个示例的结果不同的原因是因为列表是可变的但整数不是

这意味着当您就地修改整数对象时,运算符必须 return 一个新的整数对象。但是,由于列表是可变的,因此只需将更改添加到已经存在的列表对象中即可。

当您在第一个示例的 for 循环中使用 *= 时,Python 修改已经存在的列表。但是,当您将 *= 与整数一起使用时,必须 return 编辑一个新的整数对象。

这也可以通过一个简单的例子来观察:

>>> a = 1
>>> b = [1]
>>> 
>>> id(a)
1505450256
>>> id(b)
52238656
>>> 
>>> a *= 1
>>> b *= 1
>>> 
>>> id(a)
1505450256
>>> id(b)
52238656
>>>

正如您在上面看到的,a 的内存地址在我们就地相乘时发生了变化。所以 *= return 编辑了一个新对象。但是列表的内存没有改变。这意味着 *= 就地修改了列表对象。