为什么原始列表会发生变化?

时间:2017-07-05 13:18:45

标签: python list augmented-assignment

运行此:

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]

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

3 个答案:

答案 0 :(得分:3)

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

  

像x + = 1这样的增强赋值表达式可以重写为x = x + 1,以实现类似但不完全相同的效果。在增强版本中,x仅评估一次。 此外,如果可能,实际操作是就地执行的,这意味着不是创建新对象并将其分配给目标,而是修改旧对象。

(大胆强调我的)。

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

  

调用这些方法来实现增强的算术分配(+=-=*=@=/=//=%=**=<<=>>=&=^=|=)。 这些方法应尝试就地执行操作(修改self)并返回结果(可能是,但不一定是self )。< / p>

在列表上使用*=会将列表对象返回相同的列表对象(self),以便“分配”回相同的名称。

另一方面,整数是不可变对象。对整数的算术运算返回 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__方法将更改应用于列表对象本身,返回对列表对象的引用。

对于整数,执行此操作:

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

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

答案 1 :(得分:1)

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

这意味着当您修改整数对象时,运算符必须返回一个新的整数对象。但是,由于列表是可变的,因此只需将更改添加到现有列表对象中。

在第一个示例中的for循环中使用*=时,Python会修改现有列表。但是当你使用*=和整数时,必须返回一个新的整数对象。

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

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

如上所示,a的内存地址在我们就地增加时会发生变化。所以*=返回了一个新对象。但是列表的内存没有改变。这意味着*=就地修改了列表对象。

答案 2 :(得分:0)

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