运行这个:
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]
为什么要修改第一个示例中的列表?
运行这个:
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]
为什么要修改第一个示例中的列表?
您正在使用扩充的赋值语句。这些对左侧命名的对象进行操作,使该对象有机会就地更新:
像 x += 1 这样的扩充赋值表达式可以重写为 x = x + 1 以实现类似但不完全相等的效果。在增强版本中,x 只被评估一次。此外,如果可能,实际操作会在原地执行,这意味着不是创建新对象并将其分配给目标,而是修改旧对象。
(粗体强调我的)。
这是通过让对象实现__i[op]__
方法来实现的,因为=*
这就是__imul__
钩子:
调用这些方法来实现增强的算术赋值(
+=
,-=
,*=
,@=
,/=
,//=
,%=
,**=
,<<=
,>>=
,&=
,^=
,|=
)。这些方法应该尝试就地执行操作(修改self
)并返回结果(可以是,但不一定是self
)。
在列表上使用会将该列表对象*=
相乘并返回相同的列表对象 ( ) 以“分配”回相同的名称。self
另一方面,整数是不可变的对象。整数的算术运算返回新的整数对象,所以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
所以现在新的整数对象被分配给i
,它独立于a
.
每个示例的结果不同的原因是因为list 是可变的,但整数不是。
这意味着当您就地修改整数对象时,运算符必须返回一个新的整数对象。但是,由于列表是可变的,因此更改只是添加到已经存在的列表对象中。
当您*=
在第一个示例中使用 for 循环时,Python 会修改已经存在的列表。但是当您使用*=
整数时,必须返回一个新的整数对象。
这也可以通过一个简单的例子来观察:
>>> a = 1
>>> b = [1]
>>>
>>> id(a)
1505450256
>>> id(b)
52238656
>>>
>>> a *= 1
>>> b *= 1
>>>
>>> id(a)
1505450256
>>> id(b)
52238656
>>>
正如你在上面看到的,a
当我们就地相乘时,内存地址发生了变化。所以*=
返回了一个新对象。但是列表的内存没有改变。这意味着*=
就地修改了列表对象。
在第一种情况下,你i
的for
循环是一个list
. 所以你是在告诉 python 嘿,拿这个ith
列表并重复两次。您基本上是重复列表 2 次,这就是*
操作员对列表所做的操作。
在第二种情况下,你i
是一个值,所以你申请*
的是一个值,而不是一个列表。