一行中的Python多个赋值语句

时间:2015-08-22 13:10:00

标签: python variable-assignment

(别担心,这不是关于解包元组的另一个问题。)

在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如何有效?如何在一行上解析和评估这些多项任务?我读错了语言参考吗?

4 个答案:

答案 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)

Mark Dickinson解释了正在发生的事情的语法,但涉及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 根据字节码文档

“实现name = TOS”

因此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

  

赋值语句计算表达式列表(请记住,这可以是单个表达式或以逗号分隔的列表,后者产生元组)并将单个结果对象从左到右分配给每个目标列表。

相关问题