为啥分配给空列表(例如 [] = "")不会出错?
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
将这两个值分别分配给foo
和bar
。您所需要的只是右侧的 sequence 或 iterable,左侧的名称列表或元组。
当你这样做时:
[] = ""
您将一个 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_TWO
和 ROT_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