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

Pau*_*zer 70 python tuples list splat python-3.x

考虑例如

squares = *map((2).__rpow__, range(5)),
squares
# (0, 1, 4, 9, 16)

*squares, = map((2).__rpow__, range(5))
squares
# [0, 1, 4, 9, 16]
Run Code Online (Sandbox Code Playgroud)

因此,在所有其他条件相等的情况下,当我们在lhs上进行排序时,会得到一个列表,而当我们在rhss上进行布局时,会得到一个元组。

为什么?

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

use*_*ica 46

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

*whatever,
Run Code Online (Sandbox Code Playgroud)

而不是列表语法:

[*whatever]
Run Code Online (Sandbox Code Playgroud)

或设置语法:

{*whatever}
Run Code Online (Sandbox Code Playgroud)

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


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

*target, = whatever
Run Code Online (Sandbox Code Playgroud)

或“列表样式”

[*target] = whatever
Run Code Online (Sandbox Code Playgroud)

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

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

first, *rest = iterable
Run Code Online (Sandbox Code Playgroud)

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


Dev*_*ngh 30

这是在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]
Run Code Online (Sandbox Code Playgroud)

在这里,元素是一个元组

In [13]: elements = *range(6),                                                                                                                                                      

In [14]: elements                                                                                                                                                                   
Out[14]: (0, 1, 2, 3, 4, 5)
Run Code Online (Sandbox Code Playgroud)

从评论和其他答案中我可以理解的是:

  • 第一种行为是保持与函数(即)使用的现有任意参数列表保持一致。*args

  • 第二个行为是能够在评估中进一步使用LHS上的变量,因此使其成为列表,可变值而不是元组更为有意义

  • 虽然PEP确实提到了原因,但似乎并没有给出任何理由。 (5认同)
  • [PEP 3132](https://www.python.org/dev/peps/pep-3132/#specification)表示`* elements,= range(6)`将在左侧创建一个列表。PEP 448说`elements = * range(6),`已在带有隐式括号的元组display_,((* range(6),))中广义解包,从而在右侧创建了一个元组。(抄送@PaulPanzer) (2认同)

Thi*_*lle 18

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

验收

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

[...]

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

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

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


hir*_*ist 13

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

from dis import dis

def a():
    squares = (*map((2).__rpow__, range(5)),)
    # print(squares)

print(dis(a))
Run Code Online (Sandbox Code Playgroud)

分解为

  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
Run Code Online (Sandbox Code Playgroud)

def b():
    *squares, = map((2).__rpow__, range(5))
print(dis(b))
Run Code Online (Sandbox Code Playgroud)

结果是

 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
Run Code Online (Sandbox Code Playgroud)

关于UNPACK_EX状态的文档

UNPACK_EX(个)

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

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

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

BUILD_TUPLE_UNPACK(计数)

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

  • 那么这是技术原因还是没有原因? (4认同)

Par*_*ngh 9

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

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

elements = *iterable
Run Code Online (Sandbox Code Playgroud)

可以看作:

elements = 1, 2, 3, 4,
Run Code Online (Sandbox Code Playgroud)

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

现在,对于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。

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

-格雷格

跳过常规

7。

我建议:

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

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

-格雷格

8。

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

--吉多(Guido van Rossum)