python - 为什么splatting会在rhs上创建一个元组,而是列出lhs上的列表?




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

RHS是一个 表达式列表 - 没有可选 () 括号的 tuple 文字。 这与 1, 2 即使没有括号)创建元组的方式相同,以及封闭 []{} 如何创建 listset*tail 只是意味着解压缩 这个 tuple

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

LHS不会创建一个值,它会将值绑定到多个名称。 使用诸如 *leading 类的全部名称,在所有情况下都不会预先知道绑定。 相反,全部包含剩余的东西。

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

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

即使LHS具有一个没有尾随名称的全名,它也是一致性 list

例如,考虑一下

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上映射时会得到一个列表,而在sps上映射时会得到一个元组。

为什么?

这是设计,如果是的话,理由是什么? 或者,如果没有,有任何技术原因吗? 或者这是怎么回事,没有特别的原因?


不是一个完整的答案,但拆解提供了一些线索:

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中的可迭代解包为单个值,其中值的总数可以小于iterable中的项数:其中一个新值将是所有剩余项的 列表

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

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

BUILD_TUPLE_UNPACK(计数)

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


您在RHS上获得元组的事实与splat无关。 splat只是解压缩你的 map 迭代器。 您解压缩的内容取决于您使用了元组语法这一事实:

*whatever,

而不是列表语法:

[*whatever]

或设置语法:

{*whatever}

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

在LHS上,splatted赋值目标总是生成一个列表。 你是否使用“元组式”并不重要

*target, = whatever

或“列表式”

[*target] = whatever

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

你在左边使用的语法是在 PEP 3132 中引入的,以支持像

first, *rest = iterable

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






splat