(别担心,这不是关于解包元组的另一个问题。)
在python中,像foo = bar = baz = 5
这样的语句将变量foo,bar和baz赋值为5.它从左到右分配这些变量,这可以通过诸如
>>> foo[0] = foo = [0]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'foo' is not defined
>>> foo = foo[0] = [0]
>>> foo
[[...]]
>>> foo[0]
[[...]]
>>> foo is foo[0]
True
但python language reference表明赋值语句的格式为
(target_list "=")+ (expression_list | yield_expression)
在分配时,首先评估expression_list
,然后进行分配。
那么鉴于foo = bar = 5
不是bar = 5
,行expression_list
如何有效?如何在一行上解析和评估这些多项任务?我读错了语言参考吗?
答案 0 :(得分:18)
所有的归功于@MarkDickinson,他在评论中回答了这个问题:
请注意
+
中的(target_list "=")+
,即一个或多个副本。在foo = bar = 5
中,有两个(target_list "=")
作品,而expression_list
部分只是5
在赋值语句中的所有target_list
作品(即看似foo =
的内容)从左到右分配到语句右端的expression_list
,之后expression_list
得到评估。
当然,通常的'tuple-unpacking'赋值语法在这种语法中有效,让你做一些事情,比如
>>> foo, boo, moo = boo[0], moo[0], foo[0] = moo[0], foo[0], boo[0] = [0], [0], [0]
>>> foo
[[[[...]]]]
>>> foo[0] is boo
True
>>> foo[0][0] is moo
True
>>> foo[0][0][0] is foo
True
答案 1 :(得分:12)
foo
的奇怪例子表明语义可能违反直觉。
在C中,=
是一个右关联运算符,它返回值作为赋值的RHS,因此当您编写x = y = 5
时,首先计算y=5
(将{赋值为5){在此过程中{1}},然后将此值(5)分配给y
。
在我读这个问题之前,我天真地认为在Python中发生了大致相同的事情。但是,在Python中x
不是表达式(例如,=
是语法错误)。所以Python必须以另一种方式实现多个赋值。
我们可以反汇编而不是猜测:
2 + (x = 5)
有关字节码指令的说明,请参见this。
第一条指令将5推到堆栈上。
第二条指令重复它 - 所以现在堆栈的顶部有两个5s
>>> import dis
>>> dis.dis('x = y = 5')
1 0 LOAD_CONST 0 (5)
3 DUP_TOP
4 STORE_NAME 0 (x)
7 STORE_NAME 1 (y)
10 LOAD_CONST 1 (None)
13 RETURN_VALUE
根据字节码文档
因此STORE_NAME(name)
实现了STORE_Name(x)
(堆栈顶部的5),在堆栈中弹出5,然后x = 5
实现STORE_Name(y)
堆栈上的其他5个。
字节码的其余部分与此没有直接关系。
在y = 5
的情况下,字节码由于列表而更复杂,但具有基本相似的结构。关键的观察是,一旦列表foo = foo[0] = [0]
被创建并放置在堆栈上,那么指令[0]
就不会在堆栈上放置DUP_TOP
的另一个副本 ,而是将另一个引用放入列表中。换句话说,在该阶段,堆栈的前两个元素是同一列表的别名。在稍微简单的情况下可以最清楚地看到这一点:
[0]
执行>>> x = y = [0]
>>> x[0] = 5
>>> y[0]
5
时,列表foo = foo[0] = [0]
首先分配给[0]
,然后将同一列表的别名分配给foo
。这就是为什么它导致foo[0]
成为循环引用。
答案 2 :(得分:3)
bar = 5
不是表达式。多重赋值是赋值语句中的单独语句;表达式是最右边=
右侧的所有内容。
考虑它的一个好方法是最右边的=
是主要的分隔符;右边的一切都是从左到右发生的,左边的一切都是从左到右发生的。
答案 3 :(得分:3)
https://docs.python.org/3/reference/simple_stmts.html#grammar-token-assignment_stmt
赋值语句计算表达式列表(请记住,这可以是单个表达式或以逗号分隔的列表,后者产生元组)并将单个结果对象从左到右分配给每个目标列表。