为什么splatting在rhs上创建一个元组,而在lhs上创建一个列表?


71

考虑例如

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上进行布局时,会得到一个元组。

为什么?

这是设计使然吗,如果是,原因是什么?否则,是否有任何技术原因?还是只是这样,没有特殊原因?


3
同样,尽管不是骗子,但也很有意义。链接
Paritosh Singh,

Answers:


50

您在RHS上获得元组的事实与splat无关。splat只会解压缩您的map迭代器。你解压由你使用的元组语法的事实决定:

*whatever,

而不是列表语法:

[*whatever]

或设置语法:

{*whatever}

您可能已经获得一个列表或一组。您刚刚告诉Python创建一个元组。


在LHS上,分散的分配目标始终会产生一个列表。是否使用“元组样式”都没关系

*target, = whatever

或“列表样式”

[*target] = whatever

目标列表的语法。该语法看起来很像用于创建列表或元组的语法,但是目标列表语法是完全不同的事情。

PEP 3132中引入了您在左侧使用的语法,以支持诸如以下的用例

first, *rest = iterable

在拆包任务中,将iterable的元素按位置分配给未加星标的目标,如果有加星标的目标,则将所有多余内容填充到列表中并分配给该目标。选择一个列表而不是一个元组可以使进一步处理变得容易。由于您的示例中只有加星标的目标,因此所有项目都位于分配给该目标的“附加”列表中。


31

这是在PEP-0448缺点中指定的

虽然*elements, = iterable使元素成为列表,但elements = *iterable,使元素成为元组。这样做的原因可能会使不熟悉该结构的人感到困惑。

同样按照:PEP-3132规范

该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上的变量,因此使其成为列表,可变值而不是元组更为有意义


5
虽然PEP确实提到了原因,但似乎并没有给出任何理由。
保罗·潘泽

是的,事实并非如此,我想自己寻找一个原因@PaulPanzer :)
Devesh Kumar Singh,

2
PEP 3132表示*elements, = range(6)将在左侧创建一个列表。PEP 448说,它elements = *range(6),已在元组显示中进行了广义拆包,(*range(6),)并带有隐式括号,从而在右侧创建了一个元组。(cc @PaulPanzer)
安德拉斯·迪克

19

有迹象表明在PEP 3132的末尾-扩展的可迭代解包

验收

在对python-3000列表进行简短讨论后[1],PEP被Guido接受为当前形式。讨论的可能更改包括:

[...]

将加星标的目标设为元组而不是列表。这将与函数的* args一致,但会使结果的进一步处理更加困难。

[1] https://mail.python.org/pipermail/python-3000/2007-May/007198.html

因此,拥有可变列表而不是不可变元组的优势似乎是原因。


13

并不是一个完整的答案,但是分解可以提供一些线索:

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

关于UNPACK_EX状态的文档

UNPACK_EX(个)

实现带有加星标目标的分配:将TOS中的可迭代项解压缩为单个值,其中值的总数可以小于可迭代项中的项数:一个新值将是一个列表所有剩余项。

计数的低字节是列表值之前的值数,计数的高字节是列表值之后的值数。结果值从右到左放入堆栈。

(强调我的)。而BUILD_TUPLE_UNPACK返回tuple

BUILD_TUPLE_UNPACK(计数)

Pops计算堆栈中的可迭代项,将它们合并到一个元组中,然后推送结果。在元组显示(* x,* y,* z)中实现可迭代的拆包。


4
那是技术原因还是没有原因?
保罗·潘泽

1
不,我在这里看不到有原因的论点...在第二种情况下具有可变性可能会有所益处...但这只是一个猜测。
hiro主角,

10

对于RHS,没有太大的问题。这里答案很好地说明了这一点:

我们可以像平常在函数调用中那样工作。它扩展了附加的可迭代对象的内容。因此,该语句:

elements = *iterable

可以视为:

elements = 1, 2, 3, 4,

这是元组初始化的另一种方式。

现在,对于LHS,是的,存在LHS使用列表的技术原因,如围绕 初始PEP 3132所述,用于扩展拆包

可以从PEP上的对话中收集原因(末尾添加)。

从本质上讲,它可以归结为几个关键因素:

  • LHS需要支持“加星标的表达”,该表达不一定仅限于结尾。
  • RHS需要允许接受各种序列类型,包括迭代器。
  • 上面两点的组合要求在将内容接受为带星号的表达式后对其进行操作/更改。
  • Guido因行为不一致而拒绝了另一种处理方法,即模仿RHS提供的迭代器,甚至不考虑实现方面的困难。
  • 考虑到上述所有因素,关于LHS的元组必须首先成为列表,然后进行转换。这样,该方法只会增加开销,并且不会引起任何进一步的讨论。

摘要:多种因素的综合影响,决定允许在LHS上列出清单,并且相互之间也有理由。


禁止不一致类型的相关摘录:

对于建议的语义,Python的一个重要用例是当您有一个可变长度记录时,其中的前几项很有趣,而其余的则不太有趣,但并非不重要。(如果您想把其余的东西扔掉,只需编写a,b,c = x [:3]而不是a,b,c,* d = x。)如果d的类型由操作固定,因此您可以依靠它的行为。

Python 2中filter()的设计中存在一个错误(通过将其转换为迭代器BTW将在3.0中修复):如果输入是一个元组,则输出也是一个元组,但是如果输入是一个列表或其他任何东西,输出是一个列表。那是完全疯狂的签名,因为这意味着您不能指望结果是列表,不能指望它是元组-如果您需要将其作为一个或另一个,则必须将其转换为一个,浪费时间和空间。请不要重复此设计错误。 -圭多


我还尝试过重新创建与上面的摘要有关的部分引用的对话。 重点矿。

1。

在参数列表中,* args耗尽迭代器,将其转换为元组。我认为,如果元组拆包中的* args没有做同样的事情,那将令人困惑。

这提出了为什么补丁会产生列表而不是元组的问题。这背后的原因是什么?

史蒂夫

2。

IMO,您可能想进一步处理生成的序列,包括对其进行修改。

格奥尔格

3。

好吧,如果这就是您的目标,那么我希望让拆包生成的不是列表,而是与您开始使用的类型相同的类型,例如,如果我以字符串开头,那么我可能希望继续使用它会更有用。字符串::-删除其他文本

4。

处理迭代器时,您事先不知道长度, 因此获取元组的唯一方法是首先生成一个列表,然后从中创建一个元组。 格雷格

5,

是的 这就是建议* args出现在元组解包末尾的原因之一。

史蒂夫

几个convos跳过

6。

我不认为返回给定的类型是应该尝试的目标,因为它只能对固定的一组已知类型起作用。给定任意序列类型,就无法知道如何使用指定的内容为其创建新实例。

-格雷格

跳过convos

7

我建议:

  • 清单返回清单
  • 元组返回元组
  • XYZ容器返回XYZ容器
  • 非容器可迭代对象返回迭代器。

您如何建议区分后两种情况? IMO尝试对其进行切片并捕获异常是不可接受的,因为它很容易掩盖错误。

-格雷格

8。

但是我希望它没什么用。它也不支持“ a,* b,c =”。实现POV中,如果RHS上未知对象,则必须先对其进行切片,然后再对其进行遍历;这可能会引起问题例如,如果对象恰好是defaultdict,则-由于x [3:]被实现为x [slice(None,3,None)],defaultdict将为您提供其默认值。我宁愿通过迭代对象直到用尽它来定义此对象,可以针对某些已知类型(例如列表和元组)对其进行优化。

--吉多(Guido van Rossum)


4

TLDR:您tuple在RHS上得到一个,因为您要一个。您可以list在LHS上找到它,因为它更容易。


重要的是要记住,RHS先于LHS进行评估-这就是a, b = b, a起作用的原因。然后,在分配分配并为LHS和RHS使用其他功能时,区别变得明显:

# RHS: Expression List
a = head, *tail
# LHS: Target List
*leading, last = a

简而言之,尽管两者看起来相似,但它们是完全不同的。RHS是从所有名称创建一个 的表达式-LHS是从一个名称到多个名称的绑定。即使您将LHS视为名称的元组,也不限制每个名称的类型。tuple tuple


RHS是一个表达式列表-tuple没有可选()括号的文字。这与如何1, 2在没有括号的情况下创建元组,以及如何封装[]{}创建list或相同set。在*tail刚刚手段拆包这个tuple

版本3.5中的新功能:表达式列表中的可迭代拆包,最初由PEP 448提出。

LHS不会创建一个值,而是将值绑定到多个名称。对于所有名称,例如*leading,绑定在所有情况下都不是预先知道的。相反,包罗万象包含任何剩余的东西。

使用alist来存储值使此操作变得简单-尾随名称的值可以从末尾有效地删除。其余的list则包含所有捕获名称的确切值。实际上,这正是CPython所做的

  • 在加星标之前收集所有用于强制性目标的物品
  • 从列表中的迭代器中收集所有剩余的项目
  • 列表中已加星标的项目之后,弹出强制性目标的项目
  • 将单个项目和调整大小的列表推送到堆栈上

即使LHS拥有一个全称的名称而没有结尾的名称,这也是list为了保持一致。


为了保持一致性,RHS和LHS始终是同一类型,即元组和列表。- 什么?你在说什么?
user2357112支持Monica19年

此外,您可能会认为RHS的工作方式使构建元组变得容易,但是实际上,Python实际上首先构建了一个列表,然后从该列表构建了一个元组。如果无法预先确定结果的大小,尝试直接构建元组会导致复杂化。
user2357112支持Monica19

@ user2357112我已进行了一些广泛的更改,以删除一些无意中暗示RHS和LHS重叠动机的红色鲱鱼。关键是LHS使用list。RHS必须是a tuple,相同的方式{1, 2}必须是a set。下面是否有某些list->tuple转换不会改变这一点。
MiyaMiyagi '19

4

使用a = *b,

如果您这样做:

a = *[1, 2, 3],

它会给:

(1, 2, 3)

因为:

  1. 开箱和其他一些东西默认会给元组,但是如果你说

[*[1, 2, 3]]

输出:

[1, 2, 3]作为一个list,因为我做了list,这样{*[1, 2, 3]}会给一个set

  1. 拆包提供了三个要素,为此,[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]

或类似的东西,这没有太多。

  1. 相当于没有 *,,但不完整,它始终会列出一个列表。

  2. 实际上,这几乎仅用于多个变量,即:

*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,部分有一个原因,在“主要部分:”部分。

概要:

就像@Devesh在PEP 0448中提到的缺点

尽管* elements,= iterable导致元素成为列表,而elements = * iterable,使元素成为元组。这样做的原因可能会使不熟悉该结构的人感到困惑。

(强调我的)

为什么要打扰,这对我们来说并不重要,如果想要列表,为什么不使用以下内容:

print([*a])

或元组:

print((*a))

和一套:

print({*a})

等等...

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.