为什么 x,y = zip (* zip (a,b))在 Python 中工作?

我喜欢 Python 的 zip()函数。经常用,太聪明了。有时我想做 zip()的反面,认为“我过去知道如何做”,然后谷歌 Python 解压缩,然后记住,一个人使用这个神奇的 *解压缩元组列表。像这样:

x = [1,2,3]
y = [4,5,6]
zipped = zip(x,y)
unzipped_x, unzipped_y = zip(*zipped)
unzipped_x
Out[30]: (1, 2, 3)
unzipped_y
Out[31]: (4, 5, 6)

到底发生了什么?那个魔法星号是干什么的?还有什么地方可以应用它,还有什么其他令人惊叹的令人敬畏的东西在 Python 中是如此神秘和难以谷歌?

40442 次浏览

星号执行 apply(在 Lisp 和 Scheme 中称为 apply)。基本上,它获取列表,并以列表的内容作为参数调用函数。

Python 中的星号在 Python 教程的 解开参数列表下有文档说明。

它对于多个 args 也很有用:

def foo(*args):
print args


foo(1, 2, 3) # (1, 2, 3)


# also legal
t = (1, 2, 3)
foo(*t) # (1, 2, 3)

并且,您可以对关键字参数和字典使用双星号:

def foo(**kwargs):
print kwargs


foo(a=1, b=2) # {'a': 1, 'b': 2}


# also legal
d = {"a": 1, "b": 2}
foo(**d) # {'a': 1, 'b': 2}

当然,你可以把这些结合起来:

def foo(*args, **kwargs):
print args, kwargs


foo(1, 2, a=3, b=4) # (1, 2) {'a': 3, 'b': 4}

非常有用的东西。

它并不总是奏效:

>>> x = []
>>> y = []
>>> zipped = zip(x, y)
>>> unzipped_x, unzipped_y = zip(*zipped)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: need more than 0 values to unpack

哎呀! 我想它需要一个头骨来吓唬它:

>>> unzipped_x, unzipped_y = zip(*zipped) or ([], [])
>>> unzipped_x
[]
>>> unzipped_y
[]

在巨蟒3中,我认为你需要

>>> unzipped_x, unzipped_y = tuple(zip(*zipped)) or ([], [])

因为 zip 现在返回一个不是 False-y 的生成器函数。

附录@bcherry 的回答:

>>> def f(a2,a1):
...  print a2, a1
...
>>> d = {'a1': 111, 'a2': 222}
>>> f(**d)
222 111

因此,它不仅适用于关键字参数(在 这种严格意义上的中) ,也适用于命名参数(又名 位置参数)。

我对 Python 非常陌生,所以最近遇到了麻烦,但它必须更多地考虑示例的呈现方式和强调的内容。

在理解 zip 示例时,给我带来问题的是在处理 zip 调用返回值时的不对称性。也就是说,当第一次调用 zip 时,返回值被分配给一个变量,从而创建一个列表引用(包含创建的元组列表)。在第二个调用中,它利用了 Python 自动解包列表(或集合)的能力将值返回到多个变量引用中,每个引用都是单独的元组。如果有人不熟悉 Python 中的工作原理,就很容易搞不清楚到底发生了什么。

>>> x = [1, 2, 3]
>>> y = "abc"
>>> zipped = zip(x, y)
>>> zipped
[(1, 'a'), (2, 'b'), (3, 'c')]
>>> z1, z2, z3 = zip(x, y)
>>> z1
(1, 'a')
>>> z2
(2, 'b')
>>> z3
(3, 'c')
>>> rezipped = zip(*zipped)
>>> rezipped
[(1, 2, 3), ('a', 'b', 'c')]
>>> rezipped2 = zip(z1, z2, z3)
>>> rezipped == rezipped2
True

当且仅当以下两个陈述为真时,(x, y) == tuple(zip(*zip(x,y)))为真:

  • xy具有相同的长度
  • xy是元组

理解这一切的一个好方法就是在每一步都打印出来:

x = [1, 2, 3]
y = ["a", "b", "c", "d"]


print("1) x, y = ", x, y)
print("2) zip(x, y) = ", list(zip(x, y)))
print("3) *zip(x, y) = ", *zip(x, y))
print("4) zip(*zip(x,y)) = ", list(zip(*zip(x,y))))

产出:

1) x, y =            [1, 2, 3] ['a', 'b', 'c', 'd']
2) zip(x, y) =       [(1, 'a'), (2, 'b'), (3, 'c')]
3) *zip(x, y) =       (1, 'a')  (2, 'b')  (3, 'c')
4) zip(*zip(x,y)) =  [(1, 2, 3), ('a', 'b', 'c')]

基本上是这样的:

  1. xy的项目根据各自的索引进行配对。
  2. 对被解包成3个不同的对象(元组)
  3. 对被传递给 zip,它将再次根据索引对每个条目进行配对:
    • 来自所有输入的第一个项目配对: (1, 2, 3)
    • 来自所有输入的第二项配对: ('a', 'b', 'c')

现在你可以理解为什么 (x, y) == tuple(zip(*zip(x,y)))在这种情况下是假的:

  • 因为 yx长,所以第一个 zip 操作从 y中删除了额外的项(因为它不能配对) ,这个变化显然会在第二个 zip 操作中再次出现
  • 类型不同,一开始我们有两个列表,现在我们有两个元组,因为 zip在元组中而不是在列表中对项

如果您不能100% 确定地理解 zip是如何工作的,那么我在这里为这个问题写了一个答案: 解压和 * 操作符