一行中的 Python 多个赋值语句

新手上路,请多包涵

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

在 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 语言参考 指出赋值语句具有以下形式

(target_list "=")+ (expression_list | yield_expression)

在赋值时,首先评估 expression_list ,然后进行赋值。

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

原文由 mwcvitkovic 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 542
2 个回答

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

注意 + 中的 (target_list "=")+ ,这意味着一个或多个副本。在 foo = bar = 5 expression_list 5 两个 (target_list "=")

赋值语句中的所有 target_list 作品(即看起来像 foo = 的东西)从左到右分配给 expression_list 的右端语句,在 expression_list 得到评估之后。

当然,通常的“元组解包”赋值语法也适用于此语法,让您可以执行诸如

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

原文由 mwcvitkovic 发布,翻译遵循 CC BY-SA 3.0 许可协议

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

在 C 中, = 是一个右结合运算符,它返回赋值的 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

有关字节码指令的说明,请参 见此

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

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

STORE_NAME(name) 根据字节码文档“实现名称= TOS”

Thus STORE_Name(x) implements x = 5 (the 5 on top of the stack), popping that 5 off the stack as it goes, after which STORE_Name(y) implements y = 5 与堆栈中的其他 5 个。

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

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

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

When foo = foo[0] = [0] is executed, the list [0] is first assigned to foo and then an alias of the same list is assigned to foo[0] .这就是它导致 foo 成为循环引用的原因。

原文由 John Coleman 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题