【问题标题】:Python Multiple Assignment Statements In One Line一行中的 Python 多个赋值语句
【发布时间】:2015-11-16 08:30:34
【问题描述】:

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

在 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 行怎么可能有效?如何解析和评估一行上的这些多个分配?我读错了语言参考吗?

【问题讨论】:

  • 注意(target_list "=")+中的+,表示一个或多个副本。在foo = bar = 5 中,有两个(target_list "=") 产生式,而expression_list 部分只是5
  • 啊哈!这就是我所缺少的。如果您将此作为答案,我可以接受。谢谢!

标签: python variable-assignment


【解决方案1】:

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

有关字节码指令的说明,请参阅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]副本,而是放置另一个reference 到列表。换句话说,在那个阶段,堆栈的顶部两个元素是同一个列表的别名。在稍微简单的情况下可以最清楚地看到这一点:

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

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

【讨论】:

  • 一开始我对右关联也有同样的想法,但我不认为这就是 python 正在做的事情。如果是,foo[0] = foo = [0] 将是一个有效的 python 语句,但它不是。相反,foo = foo[0] = [0] 是一个有效的声明 - 相当于 foo = [0]; foo[0] = foo。因此,以您的示例为例,x = y = z = 5 被以怪异的左关联方式评估为x = 5; y = 5; z = 5。剧情变厚了……
  • 有趣的想法!不过,@MarkDickinson 指出了我对上述语言参考的误读。现在整个事情都说得通了,而且你可以做类似foo, boo = foo[0], boo[0] = [0], [0] 之类的事情
  • @cvitkovm 我知道foo 案件发生了什么。循环引用的设置是因为涉及列表的分配如何复制引用而不是列表本身。感谢您提出如此有趣的问题。
  • 优秀的答案!我从 JavaScript 换了档,就像 C 一样,赋值是从右到左的情况,所以很高兴知道 Python 是相反的,从左到右。另外,JS 每次赋值后都会返回值,但是 Python 是语法错误。感谢您指出这些事情!
【解决方案2】:

bar = 5 不是表达式。多重赋值是独立于赋值语句的语句;表达式是最右边 = 右侧的所有内容。

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

【讨论】:

  • 好吧,我绝对同意这是评估代码的方式,但我已经对代码的作用感到满意。我想弄清楚的是python如何解析foo = bar = 5之类的语句,而语法似乎与语言参考中指定的内容冲突。您能否提供一个参考,说明您在哪里发现“多重赋值是与赋值语句分开的语句......”?我只能在语言参考中找到“作业说明”。
  • 检查 cpython 实现的确切作用可能会起作用。
  • @cvitkovm 我知道,因为我为 astor 项目做出了贡献,该项目将分配存储为单个 AST 节点,但可能使用 multiple targets on the left。如果您查看该链接,您会发现重构源代码需要为每个目标打印一个等号。
  • 感谢@PatrickMaupin。 Mark Dickinson 在上面指出了我误读了语言参考的地方,但很高兴知道它在 AST 中是如何发挥作用的。
【解决方案3】:

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

注意(target_list "=")+ 中的+,表示一个或多个副本。在foo = bar = 5 中,有两个(target_list "=") 产生式,而expression_list 部分只是5

赋值语句中的所有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

【讨论】:

    【解决方案4】:

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

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

    【讨论】:

    • 嗯,这在文档中得到了简洁明了的解释:D
    【解决方案5】:

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

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

    【讨论】:

      猜你喜欢
      • 2011-10-31
      • 1970-01-01
      • 2011-10-15
      • 1970-01-01
      • 2015-07-15
      • 2015-04-27
      • 1970-01-01
      • 2020-08-07
      • 1970-01-01
      相关资源
      最近更新 更多