为啥分配给空列表(例如 [] = "")不会出错?

Posted

技术标签:

【中文标题】为啥分配给空列表(例如 [] = "")不会出错?【英文标题】:Why isn't assigning to an empty list (e.g. [] = "") an error?为什么分配给空列表(例如 [] = "")不会出错? 【发布时间】:2015-07-20 17:55:49 【问题描述】:

在 python 3.4 中,我正在输入

[] = "" 

它工作正常,没有引发异常。虽然当然[] 之后不等于""

[] = ()

也可以正常工作。

"" = []

按预期引发异常,

() = ""

尽管按预期引发了异常。发生什么了?

【问题讨论】:

【参考方案1】:

你不是在比较平等。您正在分配

Python 允许您分配给多个目标:

foo, bar = 1, 2

将这两个值分别分配给foobar。您所需要的只是右侧的 sequenceiterable,左侧的名称列表或元组。

当你这样做时:

[] = ""

您将一个 empty 序列(空字符串仍然是序列)分配给一个空的名称列表。

本质上和做是一样的:

[foo, bar, baz] = "abc"

您以foo = "a"bar = "b"baz = "c" 结尾,但字符更少。

但是,您不能分配给字符串,因此分配左侧的 "" 永远不会起作用,并且始终是语法错误。

Assignment statements documentation:

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

将对象分配给目标列表,可选地用括号或方括号括起来,递归定义如下。

强调我的

Python 不会为空列表抛出语法错误实际上是一个错误!官方记录的语法不允许空的目标列表,而对于空的(),您确实会收到错误消息。见bug 23275;它被认为是无害的错误:

首先要认识到这已经存在了很长时间并且是无害的。

另见Why is it valid to assign to an empty list but not to an empty tuple?

【讨论】:

【参考方案2】:

它遵循文档中的Assignment statements section 规则,

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

如果target list是一个逗号分隔的目标列表:该对象必须是一个与目标列表中有相同数量的项目的可迭代对象,并且项目从左到右分配对,到相应的目标。

对象必须是一个序列,其项目数与目标列表中的目标数量相同,并且项目从左到右分配给相应的目标。

所以,当你说

[] = ""

"" 是一个可迭代对象(任何有效的 Python 字符串都是一个可迭代对象),并且它正在列表的元素上进行解包。

例如,

>>> [a, b, c] = "123"
>>> a, b, c
('1', '2', '3')

因为你有一个空字符串和一个空列表,所以没有什么可以解压的。所以,没有错误。

但是,试试这个

>>> [] = "1"
Traceback (most recent call last):
  File "<input>", line 1, in <module>
ValueError: too many values to unpack (expected 0)
>>> [a] = ""
Traceback (most recent call last):
  File "<input>", line 1, in <module>
ValueError: need more than 0 values to unpack

[] = "1" 的情况下,您试图将字符串"1" 解包到一个空的变量列表上。所以它抱怨“解包的值太多(预期为 0)”。

同样,在[a] = "" 的情况下,你有一个空字符串,所以没有什么可以解包的,但是你将它解包到一个变量上,这又是不可能的。这就是为什么它抱怨“需要超过 0 个值才能解压”。

除此之外,正如您所注意到的,

>>> [] = ()

也不会抛出错误,因为() 是一个空元组。

>>> ()
()
>>> type(())
<class 'tuple'>

当它在一个空列表上解包时,没有任何东西可以解包。所以没有错误。


但是,当你这样做时

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

正如错误消息所说,您正在尝试分配给字符串文字。这是不可能的。这就是您收到错误的原因。就像说

>>> 1 = "one"
  File "<input>", line 1
SyntaxError: can't assign to literal

内部

在内部,这个赋值操作会被翻译成UNPACK_SEQUENCE操作码,

>>> dis(compile('[] = ""', "string", "exec"))
  1           0 LOAD_CONST               0 ('')
              3 UNPACK_SEQUENCE          0
              6 LOAD_CONST               1 (None)

这里,由于字符串为空,UNPACK_SEQUENCE 解包0 次。但是当你有这样的东西时

>>> dis(compile('[a, b, c] = "123"', "string", "exec"))
  1           0 LOAD_CONST               0 ('123')
              3 UNPACK_SEQUENCE          3
              6 STORE_NAME               0 (a)
              9 STORE_NAME               1 (b)
             12 STORE_NAME               2 (c)
             15 LOAD_CONST               1 (None)
             18 RETURN_VALUE

序列123 被解包到堆栈中,从右到左。所以,栈顶是1,下一个是2,最后一个是3。然后从栈顶开始,将左侧表达式中的变量一一赋值。


顺便说一句,在 Python 中,这就是您可以在同一个表达式中执行多个赋值的方法。例如,

a, b, c, d, e, f = u, v, w, x, y, z

这是可行的,因为右手边的值被用来构造一个元组,然后它将被解包到左手边的值上。

>>> dis(compile('a, b, c, d, e, f = u, v, w, x, y, z', "string", "exec"))
  1           0 LOAD_NAME                0 (u)
              3 LOAD_NAME                1 (v)
              6 LOAD_NAME                2 (w)
              9 LOAD_NAME                3 (x)
             12 LOAD_NAME                4 (y)
             15 LOAD_NAME                5 (z)
             18 BUILD_TUPLE              6
             21 UNPACK_SEQUENCE          6
             24 STORE_NAME               6 (a)
             27 STORE_NAME               7 (b)
             30 STORE_NAME               8 (c)
             33 STORE_NAME               9 (d)
             36 STORE_NAME              10 (e)
             39 STORE_NAME              11 (f)
             42 LOAD_CONST               0 (None)
             45 RETURN_VALUE

但是经典的交换技术a, b = b, a 使用栈顶元素的旋转。如果您只有两个或三个元素,则使用特殊的 ROT_TWOROT_THREE 指令处理它们,而不是构造元组和解包。

>>> dis(compile('a, b = b, a', "string", "exec"))
  1           0 LOAD_NAME                0 (b)
              3 LOAD_NAME                1 (a)
              6 ROT_TWO
              7 STORE_NAME               1 (a)
             10 STORE_NAME               0 (b)
             13 LOAD_CONST               0 (None)
             16 RETURN_VALUE

【讨论】:

你也可以使用dis('[] = ""')而不调用compile() 你能描述一下如果你使用上一个例子中的方法交换三个以上的变量/元素会发生什么吗? @hexafraction 它将构建一个包含右侧所有元素的新元组,然后将它们解包到左侧的变量上。 @hexafraction:见How does swapping of members in the python tuples (a,b)=(b,a) work internally?

以上是关于为啥分配给空列表(例如 [] = "")不会出错?的主要内容,如果未能解决你的问题,请参考以下文章

简单的 Python 问题:为啥我不能将变量分配给排序列表(就地)? [复制]

为啥使用Hibernate开发web项目的时候在servlet头上使用@webServlet(name="index",urlPatterns="/index&q

为啥扩展切片分配不如常规切片分配灵活?

Python 列表到按位运算

为啥 "map! <C-q> :q <CR> " 在 vi​​m 中不起作用?

Python列表赋值