可变类型的链式赋值

Chained assignment for mutable types

运行在调试一段代码时遇到这个问题。如果之前没有意识到这种行为。

foo = bar = [1, 2, 3]

hex(id(foo))
Out[121]: '0x1f315dafe48'
hex(id(bar))
Out[122]: '0x1f315dafe48'

两个“变量”都指向同一个内存位置。但是现在如果一个改变了,另一个也会改变:

foo.append(4)

bar
Out[126]: [1, 2, 3, 4]

所以基本上这里我们有两个名称分配给同一个 variable/memory 地址。这不同于:

foo = [1, 2, 3]
bar = [1, 2 ,3]
hex(id(foo))
Out[129]: '0x1f315198448'
hex(id(bar))
Out[130]: '0x1f319567dc8'

此处更改 foobar 不会对另一个产生任何影响。

所以我的问题是:为什么这个特性(可变类型的链式赋值)甚至存在于 Python 中?除了给你搬起石头砸自己的脚的工具之外,它还有什么用吗?

对于像

这样的简单、常见的初始化很有用
foo = bar = baz = 0

所以你不必写

foo = 0
bar = 0
baz = 0

因为它是一个语法特性,所以让它只对不可变类型起作用是不太可行的。解析器无法判断末尾的表达式是可变类型还是不可变类型。你可以拥有

def initial_value():
    if random.choice([True, False]):
        return []
    else:
        return 0

foo = bar = baz = initial_value()

initial_value() 可以 return 可变或不可变值。分配的解析器不知道它会是什么。

有很多方法可以用多次引用可变值来搬起石头砸自己的脚,Python 不会特意阻止你。有关一些更常见的示例,请参阅 "Least Astonishment" and the Mutable Default Argument and List of lists changes reflected across sublists unexpectedly

您只需要记住,在链式赋值中,值表达式只计算一次。所以你的赋值相当于

temp = [1, 2, 3]
foo = temp
bar = temp

而不是

foo = [1, 2, 3]
bar = [1, 2, 3]

How do chained assignments work?

要记住的更一般的规则是 Python 永远不会自发地复制对象,你总是必须告诉它这样做。