【问题标题】:Why is it valid to assign to an empty list but not to an empty tuple?为什么分配给一个空列表而不是一个空元组是有效的?
【发布时间】:2015-07-04 09:18:25
【问题描述】:

这出现在a recent PyCon talk

声明

[] = []

没有任何意义,但它也不会抛出异常。我觉得这一定是由于拆包规则。你也可以用列表做tuple unpacking,例如,

[a, b] = [1, 2]

做你所期望的。作为逻辑结果,当要解包的元素数量为 0 时,这也应该起作用,这可以解释为什么分配给空列表是有效的。当您尝试将非空列表分配给空列表时会发生什么,进一步支持了这一理论:

>>> [] = [1]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: too many values to unpack

如果元组也是如此,我会很高兴这个解释。如果我们可以解包到一个包含 0 个元素的列表,我们也应该能够解包到一个包含 0 个元素的元组,不是吗?然而:

>>> () = ()
  File "<stdin>", line 1
SyntaxError: can't assign to ()

似乎解包规则不适用于元组,因为它们适用于列表。对于这种不一致,我想不出任何解释。这种行为有原因吗?

【问题讨论】:

  • @ozgur 但使用元组解包确实有效:a, b = 1, 2 有效 ...
  • 我不确定,但我认为[] = [] 没有拆包。但是,当我看到这是可行的时,我感到很惊讶:[a, b] = [1, 2]。相反,我会做a, b = (1, 2)
  • 我感觉这里不会有任何有趣的原则在起作用。最好的答案可能是“这是代码生成器的部分,它检查分配的 LHS 的有效性,这是捕获 () 但让 [] 通过的检查”。可能是因为() 被识别为常量什么的。
  • @ozgur 这与突变无关 - 同样的 [] = [1]{} = {'a':1} 也会引发错误。它可能 () 的行为就像方法本身。因为[].__add_([[]]) 结果[[]] 但是,().__add__(((()))) 只是()。
  • 有一个可追溯到 2015 年 1 月的 python bug,显示了 Python 源代码中的原因。请注意,根据文档,这两个分配都应该是非法的。讨论似乎略微倾向于允许分配给()

标签: python iterable-unpacking


【解决方案1】:

我决定尝试使用dis 来弄清楚这里发生了什么,当我绊倒了一些奇怪的东西时:

>>> def foo():
...   [] = []
... 
>>> dis.dis(foo)
  2           0 BUILD_LIST               0
              3 UNPACK_SEQUENCE          0
              6 LOAD_CONST               0 (None)
              9 RETURN_VALUE        
>>> def bar():
...   () = ()
... 
  File "<stdin>", line 2
SyntaxError: can't assign to ()

不知何故,Python 编译器 对 LHS 上的空元组进行了特殊处理。这种差异与the specification 不同,它指出:

将对象分配给单个目标的递归定义如下。

...

  • 如果目标是括在括号或方括号中的目标列表:该对象必须是与目标列表中的目标具有相同数量的项目的可迭代对象,并且其项目从左到右分配,到相应的目标。

因此,您似乎在 CPython(2.7.8 和 3.4.1 测试)中发现了一个合法但最终无关紧要的错误。

IronPython 2.6.1 表现出相同的差异,但 Jython 2.7b3+ 有一个奇怪的行为,() = () 开始了一个看似无法结束的语句。

【讨论】:

  • According to the spec,它应该同时拒绝[] = []() = []
  • 我接受了@Blckknght 的回答,因为它准确地回答了我的问题,但是这个也很棒!以前不知道dis
【解决方案2】:

@user2357112 关于这似乎是巧合的评论似乎是正确的。 Python源码的相关部分在Python/ast.c

switch (e->kind) {
    # several cases snipped
    case List_kind:
        e->v.List.ctx = ctx;
        s = e->v.List.elts;
        break;
    case Tuple_kind:
        if (asdl_seq_LEN(e->v.Tuple.elts))  {
            e->v.Tuple.ctx = ctx;
            s = e->v.Tuple.elts;
        }
        else {
            expr_name = "()";
        }
        break;
    # several more cases snipped
}
/* Check for error string set by switch */
if (expr_name) {
    char buf[300];
    PyOS_snprintf(buf, sizeof(buf),
                  "can't %s %s",
                  ctx == Store ? "assign to" : "delete",
                  expr_name);
    return ast_error(c, n, buf);
}

tuples 有一个明确的检查长度不为零,并在它是时引发错误。 lists 没有任何此类检查,因此不会引发异常。

当分配给空元组是错误的时候,我没有看到允许分配给空列表的任何特殊原因,但也许有一些我没有考虑的特殊情况。我建议这可能是一个(微不足道的)错误,并且两种类型的行为应该相同。

【讨论】:

  • 在 python 2.7 中拥有一个长度为 0 的元组是完全合法的。
  • @clj:不在作业的左侧,它不是。
  • 我正要发布一个类似的答案。当我找到代码并看到它是对空元组的特定检查时,我感到非常惊讶,而不是碰巧适用于空元组但不适用于空列表的更一般的检查。
  • 该错误现已修复,允许右侧的元组:bugs.python.org/issue23275
【解决方案3】:

这是一个错误。

http://bugs.python.org/issue23275

但是,它似乎是无害的,所以我怀疑它会因为害怕破坏工作代码而得到修复。

【讨论】:

    【解决方案4】:

    “分配给一个列表”是错误的思考方式。

    在所有情况下,您都在解包:Python 解释器从所有三种编写方式创建解包指令,左侧不涉及列表或元组(代码由 @987654321 提供@):

    >>> def f():
    ...     iterable = [1, 2]
    ...     a, b = iterable
    ...     (c, d) = iterable
    ...     [e, f] = iterable
    ...
    >>> from dis import dis
    >>> dis(f)
      2           0 LOAD_CONST               1 (1)
                  3 LOAD_CONST               2 (2)
                  6 BUILD_LIST               2
                  9 STORE_FAST               0 (iterable)
    
      3          12 LOAD_FAST                0 (iterable)
                 15 UNPACK_SEQUENCE          2
                 18 STORE_FAST               1 (a)
                 21 STORE_FAST               2 (b)
    
      4          24 LOAD_FAST                0 (iterable)
                 27 UNPACK_SEQUENCE          2
                 30 STORE_FAST               3 (c)
                 33 STORE_FAST               4 (d)
    
      5          36 LOAD_FAST                0 (iterable)
                 39 UNPACK_SEQUENCE          2
                 42 STORE_FAST               5 (e)
                 45 STORE_FAST               6 (f)
                 48 LOAD_CONST               0 (None)
                 51 RETURN_VALUE      
    

    如您所见,所有三个语句完全相同。

    现在拆包做的基本上是:

    _iterator = iter(some_iterable)
    a = next(_iterator)
    b = next(_iterator)
    for superfluous_element in _iterator:
        # this only happens if there’s something left
        raise SyntaxError('Expected some_iterable to have 2 elements')
    

    类似于左侧的或多或少的名称。

    现在正如@blckknght 所说:编译器出于某种原因会检查左侧是否为空元组并不允许这样做,但如果它是空列表则不会。

    允许分配给 0 个名称是一致且合乎逻辑的:为什么不呢?您基本上只是断言右侧的可迭代对象是空的。这种观点似乎也成为bug report@gecko 提到的共识:让我们允许() = iterable

    【讨论】:

      猜你喜欢
      • 2021-05-05
      • 2022-01-05
      • 2020-08-09
      • 2023-04-07
      • 1970-01-01
      • 1970-01-01
      • 2015-05-31
      • 2016-06-21
      • 1970-01-01
      相关资源
      最近更新 更多