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]
(别担心,这不是关于解包元组的另一个问题。)
在 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. Infoo = bar = 5
, there are two(target_list "=")
productions, and theexpression_list
part is just5
赋值语句中的所有 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]