例如,考虑
squares = *map((2).__rpow__, range(5)),
squares
# (0, 1, 4, 9, 16)
*squares, = map((2).__rpow__, range(5))
squares
# [0, 1, 4, 9, 16]
因此,在所有其他条件相同的情况下,当我们在lhs上进行排序时会得到一个列表,而当我们在rhss上进行处理时就会得到一个元组。
为什么?
这是设计使然吗,如果是,原因是什么?否则,是否有任何技术原因?还是只是这样,没有特殊原因?
答案 0 :(得分:11)
尽管
*elements, = iterable
使元素成为列表,elements = *iterable,
使元素成为元组。原因可能会使不熟悉该结构的人感到困惑。
此PEP提议对可迭代的解包语法进行更改,从而允许指定“全包”名称,该名称将被分配所有未分配给“常规”名称的项目的列表。
也在此处提到:Python-3 exprlists
除非是列表或集合显示的一部分,否则包含至少一个逗号的表达式列表会产生一个元组。
仅需使用尾部逗号才能创建单个元组(也称为单例);在所有其他情况下,它都是可选的。没有尾部逗号的单个表达式不会创建元组,而是会产生该表达式的值。 (要创建一个空的元组,请使用一对空的括号:()。)
这也可以在这里的一个简单示例中看到,其中列表中的元素
In [27]: *elements, = range(6)
In [28]: elements
Out[28]: [0, 1, 2, 3, 4, 5]
在这里,元素是一个元组
In [13]: elements = *range(6),
In [14]: elements
Out[14]: (0, 1, 2, 3, 4, 5)
从我的评论和其他答案中我可以理解:
第一个行为是与函数即*args
第二个行为是能够在评估中进一步使用LHS上的变量,因此将其作为列表,可变值而不是元组更有意义
答案 1 :(得分:7)
有迹象表明在PEP 3132 -- Extended Iterable Unpacking末尾的原因:
接受
在python-3000列表[1]上简短讨论之后, PEP已被Guido接受为当前形式。可能的变化 讨论的是:
[...]
将加星标的目标设为元组而不是列表。这将是 与函数的* args一致,但需要进一步处理 结果很难。
[1] https://mail.python.org/pipermail/python-3000/2007-May/007198.html
因此,使用可变列表而不是不可变元组的优势似乎是原因。
答案 2 :(得分:6)
不是一个完整的答案,但拆卸可以提供一些线索:
from dis import dis
def a():
squares = (*map((2).__rpow__, range(5)),)
# print(squares)
print(dis(a))
反汇编为
5 0 LOAD_GLOBAL 0 (map)
2 LOAD_CONST 1 (2)
4 LOAD_ATTR 1 (__rpow__)
6 LOAD_GLOBAL 2 (range)
8 LOAD_CONST 2 (5)
10 CALL_FUNCTION 1
12 CALL_FUNCTION 2
14 BUILD_TUPLE_UNPACK 1
16 STORE_FAST 0 (squares)
18 LOAD_CONST 0 (None)
20 RETURN_VALUE
同时
def b():
*squares, = map((2).__rpow__, range(5))
print(dis(b))
产生
11 0 LOAD_GLOBAL 0 (map)
2 LOAD_CONST 1 (2)
4 LOAD_ATTR 1 (__rpow__)
6 LOAD_GLOBAL 2 (range)
8 LOAD_CONST 2 (5)
10 CALL_FUNCTION 1
12 CALL_FUNCTION 2
14 UNPACK_EX 0
16 STORE_FAST 0 (squares)
18 LOAD_CONST 0 (None)
20 RETURN_VALUE
doc on UNPACK_EX
的状态:
UNPACK_EX(计数)
带加星标目标的实现分配:将TOS中的可迭代项解压缩为单个值,其中值的总数为 小于可迭代项的数量:新项之一 值将是所有剩余项目的列表。
计数的低字节是列表值之前的值数,计数的高字节是列表值之后的值数。的 结果值从右到左放到堆栈中。
(重点是我的)。而BUILD_TUPLE_UNPACK
返回tuple
:
BUILD_TUPLE_UNPACK(计数)
Pops从堆栈中计数可迭代项,将它们合并为单个 tuple ,然后推送结果。在元组显示中实现可迭代的拆包 (* x,* y,* z)。
答案 3 :(得分:5)
您在RHS上获得元组的事实与splat无关。脚本会解压缩您的map
迭代器。您将其装入的内容是由您使用元组语法决定的:
*whatever,
代替列表语法:
[*whatever]
或设置语法:
{*whatever}
您本可以得到一个列表或一组。您只是告诉Python创建一个元组。
在LHS上,分散的分配目标始终会产生一个列表。是否使用“元组样式”都没关系
*target, = whatever
或“列表样式”
[*target] = whatever
目标列表的语法。语法看起来很像创建列表或元组的语法,但是目标列表语法却完全不同。
PEP 3132中引入了您在左侧使用的语法,以支持类似的使用案例
first, *rest = iterable
在拆包任务中,将iterable的元素按位置分配给未加星标的目标,如果有加星标的目标,则将所有多余内容填充到列表中并分配给该目标。选择了一个列表而不是一个元组,以使进一步处理变得容易。由于您的示例中只有 个已加星标的目标,因此所有项目都位于分配给该目标的“附加”列表中。
答案 4 :(得分:4)
TLDR:您在RHS上收到tuple
,因为您要一个。您会在LHS上获得list
,因为它更容易。
请切记,必须先评估RHS,然后再评估LHS,这就是a, b = b, a
起作用的原因。然后,在分配分配并为LHS和RHS使用其他功能时,区别变得明显:
# RHS: Expression List
a = head, *tail
# LHS: Target List
*leading, last = a
简而言之,虽然两者看起来相似,但它们是完全不同的。 RHS是从 all 名称创建 one tuple
的表达式-LHS是从到多个名称的绑定一个 tuple
。即使您将LHS视为名称的元组,也不会限制每个名称的类型。
RHS是expression list-tuple
文字,没有可选的()
括号。这与1, 2
在没有括号的情况下创建元组的方式以及包围[]
或{}
的方式创建list
或set
的方式相同。 *tail
只是意味着将{em>装入 tuple
。
3.5版中的新功能:表达式列表中的可迭代拆包,最初由PEP 448提出。
LHS不会创建一个值,而是将值绑定到多个名称。对于包罗万象的名称,例如*leading
,并不是在所有情况下都预先知道绑定。相反,包罗万象包含任何剩余的东西。
使用list
来存储值很容易-尾随名称的值可以有效地从末尾删除。然后,其余的list
将包含全部捕获名称的确切值。实际上,这正是CPython does:
- 在加注星标之前收集所有必需目标的物品
- 从列表中的迭代器中收集所有剩余的项目
从列表中加注星标后,- 弹出项目作为强制目标
- 将单个项目和调整大小的列表推送到堆栈上
即使LHS的名称全不包含尾随名称,它还是list
,以保持一致性。
答案 5 :(得分:4)
a = *b,
:如果您这样做:
a = *[1, 2, 3],
它将给出:
(1, 2, 3)
原因:
解包和其他一些东西在默认情况下会给出元组,但如果您说的是
[*[1, 2, 3]]
输出:
[1, 2, 3]
作为list
,因为我做了list
,所以{*[1, 2, 3]}
将给出set
。
解包给出了三个元素,而对于[1, 2, 3]
来说,确实可以做到
1, 2, 3
哪个输出:
(1, 2, 3)
这就是拆箱的目的。
主要部分:
解包只需执行:
1, 2, 3
针对:
[1, 2, 3]
这是一个元组:
(1, 2, 3)
实际上,这将创建一个列表,并将其更改为元组。
*a, = b
:好吧,这真的是:
a = [1, 2, 3]
因为不是:
*a, b = [1, 2, 3]
或类似的东西,对此没什么。
它等同于没有,但不完整,它总是会给出一个列表。*
和,
这实际上几乎只用于多个变量,即:
*a, b = [1, 2, 3]
一件事是,不管它存储的是什么列表类型:
>>> *a, = {1,2,3}
>>> a
[1, 2, 3]
>>> *a, = (1,2,3)
>>> a
[1, 2, 3]
>>>
同时拥有它也会很奇怪:
a, *b = 'hello'
并且:
print(b)
成为:
'ello'
然后看起来就不那么飞溅了。
list
还具有比其他功能更多的功能,更易于处理。
可能没有理由发生这种情况,这实际上是Python的决定。
a = *b,
部分在“主要部分:”部分中是有原因的。
在PEP 0448 disadvantages中也提到了@Devesh:
虽然元素* =可迭代,但使元素成为列表,元素= *可迭代,使元素为元组。原因可能会使不熟悉该结构的人感到困惑。
(重点是我的)
为什么要打扰,这对我们来说并不重要,如果想要列表,为什么不直接使用以下内容:
print([*a])
或元组:
print((*a))
还有一组:
print({*a})
依此类推...
答案 6 :(得分:2)
对于RHS,没有太大的问题。 answer here说得很好:
我们具有它在函数调用中通常的工作方式。它扩大 它附加到的iterable的内容。因此,声明:
elements = *iterable
可以被视为:
elements = 1, 2, 3, 4,
这是初始化元组的另一种方法。
现在,对于LHS, 是的,如有关the initial PEP 3132 for extending unpacking
的讨论中所述,LHS使用列表是有技术原因的可以从PEP上的对话中收集原因(末尾添加)。
从本质上讲,它可以归结为几个关键因素:
总结:多种因素共同导致了允许在LHS上列出清单的决定,以及彼此之间产生分歧的原因。
用于禁止类型不一致的相关摘录:
在Python中,所建议的语义的重要用例是何时 您有一个可变长度的记录,其中的前几项是 有趣,其余的则不太重要,但并非不重要。 (如果您想把剩下的扔掉,您只需写a,b,c = x [:3]代替a,b,c,* d = x。)这样做更加方便 用例,如果d的类型是由操作确定的,则可以计算 关于其行为。
Python 2中的filter()设计中存在一个错误( 通过将其转换为迭代器BTW在3.0中修复):如果输入是 元组,输出也是元组,但是如果输入是列表或 其他,输出为列表。太疯狂了 签名,因为这意味着您不能指望结果是 列表,也不是一个元组-如果您需要它是一个或 其他,您必须将其转换为一个,这是浪费时间, 空间。请不要重复此设计错误。 -圭多(Guido)
我还尝试过重新创建与上述摘要相关的部分引用的对话。Source 强调地雷。
1。
在参数列表中,* args耗尽迭代器,将其转换为 元组。我认为如果* args在元组解包中会令人困惑 没有做同样的事情。
这提出了一个问题,即为什么补丁会生成列表,而不是列表 元组。这是什么原因?
STEVe
2。
IMO,您可能想进一步处理结果 顺序,包括对其进行修改。
乔治
3。
好吧,如果这就是您的目标,那么我希望它会更多 使拆包生成不是列表,而是与您使用的相同类型很有用 开头,例如如果我以字符串开头,我可能想 继续使用字符串:: -其他文字被剪掉
4。
使用迭代器进行交易时,您事先不知道长度, 因此获取元组的唯一方法是先生成列表,然后 然后从中创建一个元组。 格雷格
5。
是的。这是建议* args 的原因之一 应该只在元组解包的末尾出现。
STEVe
跳过了几个convos
6。
我不认为返回给定的类型应该是一个目标 之所以尝试,是因为它只能用于一组固定的已知值 类型。给定任意序列类型,就无法知道 如何创建具有指定内容的新实例。
-格雷格
跳过常规
7。
我建议:
- 列表返回列表
- 元组返回元组
- XYZ容器返回XYZ容器
- 非容器可迭代对象返回迭代器。
您打算如何区分后两种情况? 尝试对其进行切片并捕获异常, IMO,因为它很容易掩盖错误。
-格雷格
8。
但是我希望它没什么用。 它将不支持“ a,* b,c =” 。如果您在实现POV 中有一个未知对象, 在RHS中,您必须先对其进行切片,然后再尝试对其进行迭代; 这可能会引起问题,例如如果对象碰巧是defaultdict -由于x [3:]被实现为x [slice(None,3,None)],defaultdict将为您提供其默认值。我宁愿定义 就迭代对象直到用尽为止 可以针对某些已知类型(例如列表和元组)进行优化。
- -吉多·范·罗苏姆(Guido van Rossum)