Python 可变类型的链式赋值
调试一段代码时遇到此问题。如果之前没有意识到这种行为Python 可变类型的链式赋值,python,chained-assignment,Python,Chained Assignment,调试一段代码时遇到此问题。如果之前没有意识到这种行为 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] 因此,本质上这里我们有两个名称分配给同一个变量/内存地址。这与: foo
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]
因此,本质上这里我们有两个名称分配给同一个变量/内存地址。这与:
foo = [1, 2, 3]
bar = [1, 2 ,3]
hex(id(foo))
Out[129]: '0x1f315198448'
hex(id(bar))
Out[130]: '0x1f319567dc8'
在这里,对foo
或bar
的更改不会对另一个产生任何影响
所以我的问题是:为什么这个特性(可变类型的链式赋值)甚至存在于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()
可以返回可变或不可变的值。分配的解析器无法知道它将是什么
有很多方法可以通过多次引用可变值来打击你自己,Python不会特意阻止你。有关一些更常见的示例,请参见和
您只需记住,在链式赋值中,值表达式只计算一次。所以你的作业相当于
temp = [1, 2, 3]
foo = temp
bar = temp
而不是
foo = [1, 2, 3]
bar = [1, 2, 3]
看
需要记住的一条更一般的规则是Python从不自发地复制对象,您必须始终告诉它这样做。这适用于不可变对象,因为它们是有效的指针。如果执行
foo+=1
,则foo
现在将指向不同的内存地址,而bar
和baz
仍将指向存储值0的原始地址。它永远不会导致不可变的问题,但可变类型是另一回事。我不确定我是否同意“解析器不能判断最后的表达式是可变的还是不可变的类型”。如果解析器在赋值的另一端看到[]
或{}
,它应该立即意识到它正在处理一个可变的。foo=bar=baz=somefunc()
这取决于somefunc()
是否返回一个可变或不可变的对象。foo=bar=baz=[]如果f()否则0
Iff()
已定义并且可以返回值解析器应该知道返回值的类型,不是吗?foo=bar=[1,2,3]
的行为与行为一致,或者bar=[1,2,3]
后跟foo=bar
@MarkMeyer,同意。但你为什么要这么做,因为你知道对foo的任何改变都会改变bar呢?给同一个变量分配两个名称有什么意义?我不知道。我很确定我从未在现实生活中使用过这种分配模式。另一方面,我真的不希望我的代码分配(可能)大型集合而不显式复制。如果调用list.sort(foo)
,您更希望list.sort
函数引用与foo
相同的列表,而不是它的副本;否则它将对自己的副本进行排序,并且foo
保持不变。当然,list.sort
中必须有一个局部变量,它与foo
不同(可能称为self
)。因此,有很多原因让两个不同的变量引用同一个可变对象。