【问题标题】:[] = (), () = (), and {} = () 'assignments'[] = ()、() = () 和 {} = () '作业'
【发布时间】:2018-07-04 21:31:05
【问题描述】:

我惊讶地发现,在 Python 3 中,前两个没有任何问题:

>>> [] = ()
>>> () = ()
>>> {} = ()
  File "<stdin>", line 1
SyntaxError: can't assign to literal

在 Python 2.7 中,只有第一个不会引发任何问题:

>>> [] = ()
>>> () = ()
  File "<stdin>", line 1
SyntaxError: can't assign to ()
>>> {} = ()
  File "<stdin>", line 1
SyntaxError: can't assign to literal

这里发生了什么?为什么其中任何一个都不会引发错误?为什么() = () 可能被添加为在 Python 3 中有效?

*注意,您可以将右侧替换为任何空的可迭代对象(例如[] = set()),我只是为插图选择了一个空元组

【问题讨论】:

  • 我知道这是关于拆包的。然后我想,在 JavaScript 中这肯定是不可能的(那里称为解构)。我完全惊讶于 let [] = [];({}) =&gt; 1 这样的东西在 JS 中是有效的。
  • 好问题,直到现在才意识到这是可能的

标签: python python-3.x python-2.7


【解决方案1】:

根据Issue23275,这些基本上是怪癖,不会造成真正的伤害,但也没有实用性。请注意,[] = () 不会改变 list 文字:

>>> [] = ()
>>> type([])
<class 'list'>

[] = x 语句基本上断言x 是可迭代的并且x 是空的(尽管没有人会建议以这种方式使用它们),例如

>>> [] = (1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'int' object is not iterable
>>> [] = (1,)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: too many values to unpack

作为John Y cmets,最好将[] = () 视为不是赋值,而是与Python 的可迭代解包语法保持一致的一种方式。

作为ArrowCase cmets,这个语法也扩展到多个赋值:

>>> a = [] = ()
>>> a
()

查看多重赋值的 CPython 字节码说明此操作类似于普通的可迭代解包语法,使用 UNPACK_SEQUENCE 指令:

>>> dis.dis('a = [] = ()')
  1           0 BUILD_TUPLE              0
              2 DUP_TOP
              4 STORE_NAME               0 (a)
              6 UNPACK_SEQUENCE          0
              8 LOAD_CONST               0 (None)
             10 RETURN_VALUE
>>> dis.dis('[a, b] = (1, 2)')
  1           0 LOAD_CONST               3 ((1, 2))
              2 UNPACK_SEQUENCE          2
              4 STORE_NAME               0 (a)
              6 STORE_NAME               1 (b)
              8 LOAD_CONST               2 (None)
             10 RETURN_VALUE

同样的 Issue23275 指出 () = () 已作为有效语法添加到 Python 3 以保持一致。决定删除[] = () 会不必要地破坏代码,因为它不会造成任何伤害并且符合可迭代的解包逻辑。 {} = () 仍然无效,因为在这种带有大括号的上下文中,解包语法没有意义。

如果有人想知道,像list() = () 这样的语法在语法上是无效的,因为你永远不能分配给函数调用。

【讨论】:

  • 您不能将空元组分配(或解包)到字典,这不足为奇。
  • @Munir 所以你说你不能分配给一个字典,因为你不能分配给一个字典?不是一个很有启发性的解释......
  • @StefanPochmann - 您也不能实际上分配给元组或列表。但这些是序列,因此您可以解包(按顺序分配)到它们的元素。字典不是序列,它本身没有“元素”,它有键值对。
  • @Munir 但是那些对给定的迭代没有做任何事情。
  • @Munir 不,不一样。你可以{}[0] = (1, 2) 但不能[] = (1, 2)
【解决方案2】:

赋值语句的左侧不是表达式,它是target list。简短总结:

  • 如果目标列表是标识符,则名称仅绑定到右侧。
  • 如果目标列表是逗号分隔的目标列表,则右侧是解包的,解包后的元素将分配给列出的目标。
  • 目标列表可以用圆括号或方括号括起来。特别是,这允许创建空目标列表,如您的示例中所示。

这就解释了为什么[]() 是赋值的有效左侧:它们是有效的目标列表。但是,{} 不是,因为它不是有效的目标列表。

当然,{} 可能是目标的一部分,例如作为订阅的主要部分:{}[()] = 0 是有效的 Python(当然,完全没用)。

【讨论】:

    【解决方案3】:

    这是将一个包含两个元素的可迭代对象解压缩为两个赋值目标的语法:

    [x, y] = whatever
    

    这可以概括三个或更多目标,但也可以向下概括:

    [x] = whatever
    

    将一个单元素可迭代对象解包到一个赋值目标中,并且

    [] = whatever
    

    将一个零元素可迭代对象解包到零赋值目标中(如果whatever 是一个零元素可迭代对象,则不执行任何操作,否则抛出异常)。

    () = whatever 也解包一个零元素可迭代,但{} = whatever 没有;没有涉及大括号的解包赋值语法。

    【讨论】:

      【解决方案4】:

      有一种方法可以从可迭代对象中分配变量:

      >>> a, b = iter((1, 2))
      >>> a
      1
      >>> b
      2
      >>> [c, d] = iter((4, 5))
      >>> c
      4
      >>> d
      5
      

      [] = …() = … 分配似乎是这些的特例。

      【讨论】:

      • 您实际上不必说iter(...)。你可以简单地做a, b = 1, 2
      • @DeepSpace glglgl 不可能不知道这一点。在我看来是故意的,有点“匿名”元组,强调它可以是任何可迭代的。
      • @StefanPochmann 对。首先,我想用itertools 做一些事情,然后寻找limit 之类的东西,但是产生正确数量的项目的任意迭代器对我来说似乎没问题。
      • 这应该是公认的答案。这种行为也解释了为什么[] = {}() = {} 是可能的,但反过来不行。字典中的值可以迭代并分配给列表/元组中的变量,但不能反转,因为字典不能像其他两个一样用于保存“变量列表”来分配。
      • 将有问题的情况称为退化而不是特殊情况会更好。它们源于对行为的一致应用。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-09-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多