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

时间:2019-05-21 11:54:12

标签: python python-3.x list tuples splat

例如,考虑

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上进行处理时就会得到一个元组。

为什么?

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

7 个答案:

答案 0 :(得分:11)

这是在PEP-0448 disadvantages

中指定的
  

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

也按照:PEP-3132 specification

  

此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

  • 中使用的现有arbitrary argument lists保持一致。
  • 第二个行为是能够在评估中进一步使用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在没有括号的情况下创建元组的方式以及包围[]{}的方式创建listset的方式相同。 *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. 解包和其他一些东西在默认情况下会给出元组,但如果您说的是

    [*[1, 2, 3]]

    输出:

    [1, 2, 3]作为list,因为我做了list,所以{*[1, 2, 3]}将给出set

  2. 解包给出了三个元素,而对于[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,部分在“主要部分:”部分中是有原因的。

摘要:

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

总结:多种因素共同导致了允许在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)