理解 * x,= lst

我在查阅一些旧代码,试图理解它的功能,我发现了一个奇怪的声明:

*x ,= p

p是这种上下文中的一个列表。我一直在想这份声明的作用。据我所知,它只是将 x设置为 p的值。例如:

p = [1,2]
*x ,= p
print(x)

只是给予

[1, 2]

这和 x = p有什么不同吗? 知道这个语法在做什么吗?

8951 次浏览

*x ,= p基本上是使用 扩展可迭代拆包装x = list(p)的模糊版本。x之后的逗号是使赋值目标为 tuple 所必需的(但它也可以是一个列表)。

*x, = p 不同于 x = p,因为前者创建 p收到(即一个新列表) ,而后者创建原始列表的 参考文献。举例说明:

>>> p = [1, 2]
>>> *x, = p
>>> x == p
True
>>> x is p
False
>>> x = p
>>> x == p
True
>>> x is p
True

这是 Python 3.0(PEP 3132)中引入的一个特性:

>>> p = [1, 2, 3]
>>> q, r, s = p
>>> q
1
>>> r
2
>>> s
3

Python 3对此进行了扩展,使得一个变量可以保存多个值:

>>> p = [1, 2, 3]
>>> q, *r = p
>>> q
1
>>> r
[2, 3]

因此,这就是在这里使用的东西。但是,它只是一个变量,而不是两个变量来保存三个值,它接受列表中的每个值。这与 x = p不同,因为 x = p只是意味着 xp的另一个名字。但是,在这种情况下,它是一个新列表,其中恰好包含相同的值。(你可能会对 “最小惊讶”与易变的违约论证感兴趣)

产生这种效果的另外两种常见方式是:

>>> x = list(p)

还有

>>> x = p[:]

自 Python 3.3以来,list 对象实际上有一个用于复制的方法:

x = p.copy()

切片实际上是一个非常相似的概念。然而,正如 nnewneo 指出的那样,这只适用于诸如支持切片的列表和元组之类的对象。但是,您提到的方法适用于任何迭代器: 字典、集合、生成器等。

你应该总是把这些抛给 dis,看看它会抛给你什么; 你会看到 *x, = p实际上和 x = p有什么不同:

dis('*x, = p')
1           0 LOAD_NAME                0 (p)
2 UNPACK_EX                0
4 STORE_NAME               1 (x)

而简单的赋值语句:

dis('x = p')
1           0 LOAD_NAME                0 (p)
2 STORE_NAME               1 (x)

(去掉不相关的 None返回)

正如你所看到的,UNPACK_EX是两者之间不同的操作码; 记录在案的是:

使用带星号的目标实现赋值: 将 TOS 中的迭代文件(堆栈顶部)解压缩为单独的值,其中值的总数可以小于迭代文件中的项目数: 新的值之一将是所有剩余项目的列表。

这就是为什么,正如 Eugene 指出的,你得到一个新的对象,它的名字是 x,而不是一个已经存在的对象的引用(就像 x = p一样)。


*x,看起来确实很奇怪(这里多了一个逗号) ,但这里需要它。左边必须是 tuple 或 list,由于在 Python 中创建单个元素 tuple 的特殊性,您需要使用后面的 ,:

i = 1, # one element tuple

如果你喜欢让人困惑,你可以使用 list版本的这个:

[*x] = p

完全一样但是没有多余的逗号。

你可以从下面的例子中清楚地理解它

L = [1, 2, 3, 4]
while L:
temp, *L = L
print(temp, L)

它的作用是,每次前面的变量都会得到第一个条目,剩下的列表会给 L。

输出如下所示。

1 [2, 3, 4]
2 [3, 4]
3 [4]
4 []

也看看下面的例子

x, *y, z = "python"
print(x,y,z)

在这两个 x 中,z 将从字符串中得到每一个字母,这意味着第一个字母被赋值给 x,最后一个字母被赋值给 z,剩下的字符串将被赋值给变量 y。

p ['y', 't', 'h', 'o'] n

再举一个例子,

a, b, *c = [0,1,2,3]
print(a,b,c)


0 1 [2,3]

边界情况: 如果没有剩余的星型变量,那么它将得到一个空列表。

例如:

a,b=[1]
print(a,b)


1 []