Python 一行中的多个赋值语句

Python Multiple Assignment Statements In One Line

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

在 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 首先被评估然后分配发生。

既然 bar = 5 不是 expression_list,那行 foo = bar = 5 怎么可能有效呢?如何解析和评估一行中的这些多个分配?我读错了语言参考吗?

Mark Dickinson 解释了正在发生的事情的语法,但是涉及 foo 的奇怪示例表明语义可能是违反直觉的。

在 C 中,= 是一个右结合运算符,它 returns 作为赋值的 RHS 值,所以当你写 x = y = 5 时,y=5 是第一个评估(在此过程中将 5 分配给 y),然后将此值 (5) 分配给 x.

在我读到这个问题之前,我天真地假设在Python中也发生了大致相同的事情。但是,在 Python = 中不是 表达式(例如,2 + (x = 5) 是语法错误)。所以Python必须换一种方式实现多重赋值

我们可以拆解而不是猜测:

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

有关字节码指令的说明,请参阅 this

第一条指令将 5 压入堆栈。

第二条指令复制它 -- 所以现在栈顶有两个 5

STORE_NAME(name) "Implements name = TOS" 根据字节码文档

因此 STORE_Name(x) 实现 x = 5(堆栈顶部的 5),随着它的推移从堆栈弹出 5,之后 STORE_Name(y) 实现 y = 5与堆栈中的其他 5 个。

字节码的其余部分与此处不直接相关。

foo = foo[0] = [0] 的情况下,字节码由于列表而更加复杂,但具有基本相似的结构。关键的观察是,一旦创建了列表 [0] 并将其放入堆栈,那么指令 DUP_TOP 就不会放置 [0] 的另一个 copy在堆栈上,而是将另一个 reference 放置到列表中。换句话说,在那个阶段,栈顶的两个元素是同一个列表的别名。在更简单的情况下可以最清楚地看到这一点:

>>> x = y = [0]
>>> x[0] = 5
>>> y[0]
5

执行foo = foo[0] = [0]时,首先将列表[0]分配给foo,然后将同一列表的别名分配给foo[0]。这就是导致 foo 成为循环引用的原因。

bar = 5 不是表达式。多重赋值是一个独立于赋值语句的语句;表达式是最右边 =.

右边的所有内容

考虑它的一个好方法是最右边的 = 是主要分隔符;它右边的一切都是从左到右发生的,它左边的一切也是从左到右发生的。

所有功劳归功于@MarkDickinson,他在评论中回答了这个问题:

Notice the + in (target_list "=")+, which means one or more copies. In foo = bar = 5, there are two (target_list "=") productions, and the expression_list part is just 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

https://docs.python.org/3/reference/simple_stmts.html#grammar-token-assignment_stmt

An assignment statement evaluates the expression list (remember that this can be a single expression or a comma-separated list, the latter yielding a tuple) and assigns the single resulting object to each of the target lists, from left to right.

赋值顺序是从左到右将最右边的值赋给第一个变量。请注意以下内容:

>>> foo[0] = foo = [1,2,3] # line 1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  NameError: name 'foo' is not defined
>>> foo = foo[0] = [1,2,3] # line 2
>>> foo
[[...], 2, 3]

第 1 行的赋值失败,因为它试图为 foo[0] 赋值,但 foo 从未被初始化或定义,因此它失败了。 第 2 行的赋值有效,因为 foo 首先被初始化为 [1,2,3],然后 foo[0] 被赋值 [1,2,3]