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



>例如,考虑一下,

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 上撒布时会得到一个列表,在 rhs 上撒布时会得到一个元组。

为什么?

这是设计使然,如果是,理由是什么?或者,如果没有,是否有任何技术原因?还是就是这样,没有特别的原因?

你在 RHS 上得到一个元组的事实与 splat 无关。splat 只是解开您的map迭代器。你把它解压缩什么取决于你使用了元组语法的事实:

*whatever,

而不是列表语法:

[*whatever]

或设置语法:

{*whatever}

你可以得到一个列表或一组。你刚刚告诉 Python 创建一个元组。


在 LHS 上,散放的分配目标始终生成一个列表。是否使用"元组样式"并不重要

*target, = whatever

或"列表样式">

[*target] = whatever

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

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

first, *rest = iterable

在解包分配中,可迭代对象的元素按位置分配给未加星标的目标,如果有已加星标的目标,则任何额外内容都将填充到列表中并分配给该目标。选择了列表而不是元组,以便于进一步处理。由于示例中只有一个已加星标的目标,因此所有项目都位于分配给该目标的"额外"列表中。

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

在PEP 3132 -- 扩展可迭代解包的末尾,有一个迹象表明了原因:

接受

在对 python-3000 列表 [1] 进行了简短的讨论之后, PEP以目前的形式被圭多接受。可能的更改 讨论的是:

[...]

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

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

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

不是一个完整的答案,但反汇编提供了一些线索:

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)。

对于 RHS,没有太大的问题。 这里的答案说得很好:

我们让它像通常在函数调用中一样工作。它扩展 它所附加到的可迭代对象的内容。所以,声明:

elements = *iterable

可以看作是:

elements = 1, 2, 3, 4,

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

现在,对于LHS, 是的,LHS 使用列表是有技术原因的,如围绕初始 PEP 3132 扩展开箱的讨论所示

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

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

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

总结: 各种因素的结合导致决定允许在LHS上列出名单,原因相互影响。


不允许不一致类型的相关摘录:

Python 中提出的语义的重要用例是 您有一个可变长度记录,其中前几项是 有趣,其余的就不那么有趣了,但并非不重要。 (如果你想扔掉其余的,你只需写a,b,c = x[:3] 而不是 a, b, c, *d = x.)这样做要方便得多 用例,如果 d 的类型由操作固定,因此您可以计数 关于其行为。

Python 2 中 filter() 的设计中存在一个错误(这将是 在 3.0 中通过将其转换为迭代器来修复 顺便说一句):如果输入是 元组,输出也是一个元组,但如果输入是一个列表或 其他任何内容,输出都是一个列表。 这完全是疯了 签名,因为这意味着你不能指望结果是 列表,也不是一个元组 -如果你需要它是一个元组或 其他,您必须将其转换为一个,这是浪费时间并且 空间。请不要重复这个设计错误。-圭多


我还试图重新创建一个与上述摘要相关的部分引用的对话。源强调我的。

1.

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

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

史蒂夫

阿拉伯数字。

IMO,您可能希望进一步处理结果 序列,包括修改它。

乔治

3.

好吧,如果这就是你的目标,那么我希望它会更多 有用的是让解包生成不是列表,而是相同类型 开头,例如,如果我以字符串开头,我可能想要 继续使用字符串:: --截取了附加文本

4.

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

5.

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

史蒂夫

跳过了情侣会议

6.

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

--格雷格

跳过的会议

7.

我建议:

  • 列出返回列表
  • 元组返回元组
  • XYZ 容器返回 XYZ 容器
  • 非容器可迭代对象返回迭代器。

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

--格雷格

8.

但我预计用处不大。它不支持"a, *b, c = " 也。实现 POV,如果您有未知对象 RHS,在尝试迭代它之前,您必须尝试对其进行切片; 这可能会导致问题,例如,如果对象恰好是默认参数 -- 由于 x[3:] 实现为 x[slice(None, 3, None)],默认字典将给你它的默认值。我宁愿定义 这是在迭代对象直到它耗尽方面, 可以针对某些已知类型(如列表和元组)进行优化。

-- --圭多·范·罗苏姆

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 是一个表达式,用于从所有名称创建一个tuple- LHS 是绑定到一个tuple多个名称。即使您将 LHS 视为名称元组,也不会限制每个名称的类型。


RHS 是一个表达式列表 - 一个没有可选()括号的tuple文本。这与1, 2创建元组(即使没有括号)以及如何将[]{}括起来创建listset相同。*tail只是意味着解开包装这个tuple.

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

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

使用list存储值使这变得简单 - 尾随名称的值可以有效地从末尾删除。然后,其余list包含 catch-all 名称的确切值。事实上,这正是CPython所做的:

  • 在加星标的目标之前收集强制性目标的所有项目
  • 从列表中的可迭代对象中收集所有剩余项
  • 从列表中加星标的目标之后弹出强制目标的项目
  • 在堆栈上推送单个项目和调整大小的列表

即使 LHS 有一个包罗万象的名称而没有尾随名称,这也是一致性的list

使用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, = 可迭代导致元素成为

列表,但元素 = *可迭代,导致元素成为元组其原因可能会使不熟悉该结构的人感到困惑。

(强调我的)

何必呢,这对我们来说并不重要,如果您想要一个列表,为什么不直接使用以下:

print([*a])

或元组:

print((*a))

还有一套:

print({*a})

等等...

相关内容

  • 没有找到相关文章

最新更新