2022年 11月 9日

彻底搞懂Python中的zip()

Python 的 zip() 函数创建了一个迭代器,它将聚合来自两个或多个可迭代对象的元素。您可以使用生成的迭代器快速一致地解决常见的编程问题,例如创建字典。在本教程中,您将发现 Python zip() 函数背后的逻辑以及如何使用它来解决实际问题。

返回元组的迭代器,其中第 i 个元组包含来自每个参数序列或可迭代对象的第 i 个元素。当最短的输入迭代用完时,迭代器停止。使用单个可迭代参数,它返回一个 1 元组的迭代器。没有参数,它返回一个空的迭代器。

您将在本教程的其余部分中解压缩此定义。在处理代码示例时,您会看到 Python 拉链操作就像包或牛仔裤上的物理拉链一样工作。将拉链两侧的互锁齿拉在一起以闭合开口。事实上,这个视觉类比非常适合理解 zip(),因为该函数是以物理拉链命名的!

Python 的 zip() 函数定义为 zip(*iterables)。该函数将可迭代对象作为参数并返回一个迭代器。此迭代器生成一系列元组,其中包含来自每个可迭代对象的元素。 zip() 可以接受任何类型的可迭代对象,例如文件、列表、元组、字典、集合等。

传递 n 个参数

>>> numbers = [1, 2, 3]
>>> letters = ['a', 'b', 'c']
>>> zipped = zip(numbers, letters)
>>> zipped  # Holds an iterator object
<zip object at 0x7fa4831153c8>
>>> type(zipped)
<class 'zip'>
>>> list(zipped)
[(1, 'a'), (2, 'b'), (3, 'c')]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

在这里,您使用 zip(numbers, letters) 创建一个生成 (x, y) 形式的元组的迭代器。在这种情况下,x 值取自数字,y 值取自字母。请注意 Python zip() 函数如何返回迭代器。要检索最终的列表对象,您需要使用 list() 来使用迭代器。

如果您正在使用诸如列表、元组或字符串之类的序列,则可以保证从左到右评估您的可迭代对象。这意味着生成的元组列表将采用以下形式 [(numbers[0], letters[0]), (numbers[1], letters[1]),…, (numbers[n], letters[n ])]。但是,对于其他类型的可迭代对象(如集合),您可能会看到一些奇怪的结果:

>>> s1 = {2, 3, 1}
>>> s2 = {'b', 'a', 'c'}
>>> list(zip(s1, s2))
[(1, 'a'), (2, 'c'), (3, 'b')]
  • 1
  • 2
  • 3
  • 4

在这个例子中, s1 和 s2 是集合对象,它们的元素不保持任何特定的顺序。这意味着 zip() 返回的元组将包含随机配对的元素。如果您打算将 Python zip() 函数与无序的可迭代对象(如集合)一起使用,那么请记住这一点。

不传递参数

您也可以不带参数调用 zip()。在这种情况下,您只会得到一个空的迭代器:

>>> zipped = zip()
>>> zipped
<zip object at 0x7f196294a488>
>>> list(zipped)
[]
  • 1
  • 2
  • 3
  • 4
  • 5

在这里,您调用不带参数的 zip(),因此您的 zipped 变量包含一个空迭代器。如果您使用 list() 使用迭代器,那么您也会看到一个空列表。
您也可以尝试强制空迭代器直接产生一个元素。在这种情况下,你会得到一个 StopIteration 异常:

>>> zipped = zip()
>>> next(zipped)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
  • 1
  • 2
  • 3
  • 4
  • 5

当您在 zipped 上调用 next() 时,Python 会尝试检索下一项。然而,由于 zipped 包含一个空的迭代器,所以没有什么可以拉出,所以 Python 引发了一个 StopIteration 异常。

传递一个参数

Python 的 zip() 函数也可以只接受一个参数。结果将是一个生成一系列单项元组的迭代器:

>>> a = [1, 2, 3]
>>> zipped = zip(a)
>>> list(zipped)
[(1,), (2,), (3,)]
  • 1
  • 2
  • 3
  • 4

这可能不是那么有用,但它仍然有效。也许您可以找到一些 zip() 行为的用例!
如您所见,您可以根据需要使用尽可能多的输入迭代来调用 Python zip() 函数。结果元组的长度将始终等于您作为参数传递的可迭代对象的数量。这是一个包含三个可迭代对象的示例:

>>> integers = [1, 2, 3]
>>> letters = ['a', 'b', 'c']
>>> floats = [4.0, 5.0, 6.0]
>>> zipped = zip(integers, letters, floats)  # Three input iterables
>>> list(zipped)
[(1, 'a', 4.0), (2, 'b', 5.0), (3, 'c', 6.0)]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

在这里,您使用三个可迭代对象调用 Python zip() 函数,因此生成的元组每个都包含三个元素。

传递长度不等的参数

当您使用 Python zip() 函数时,请务必注意迭代的长度。您作为参数传入的可迭代对象的长度可能不同。
在这些情况下, zip() 输出的元素数量将等于最短迭代的长度。 zip() 将完全忽略任何不再可迭代的元素中的剩余元素,如您在此处看到的:

>>> list(zip(range(5), range(100)))
[(0, 0), (1, 1), (2, 2), (3, 3), (4, 4)]
  • 1
  • 2

因为 5 是第一个(也是最短的)range() 对象的长度,所以 zip() 输出一个包含五个元组的列表。第二个 range() 对象中仍有 95 个不匹配的元素。这些都被 zip() 忽略,因为第一个 range() 对象中没有更多元素来完成对。

循环多个迭代

循环多个迭代是 Python 的 zip() 函数最常见的用例之一。如果您需要遍历多个列表、元组或任何其他序列,那么您很可能会使用 zip()。本节将向您展示如何使用 zip() 同时遍历多个可迭代对象。

并行遍历列表

Python 的 zip() 函数允许您并行迭代两个或多个可迭代对象。由于 zip() 生成元组,您可以在 for 循环的标题中解压缩它们:

>>> letters = ['a', 'b', 'c']
>>> numbers = [0, 1, 2]
>>> for l, n in zip(letters, numbers):
...     print(f'Letter: {l}')
...     print(f'Number: {n}')
...
Letter: a
Number: 0
Letter: b
Number: 1
Letter: c
Number: 2
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

在这里,您遍历 zip() 返回的一系列元组并将元素解压缩为 l 和 n。当您结合 zip()、for 循环和元组解包时,您可以获得一个有用的 Python 风格的习惯用法,用于一次遍历两个或多个可迭代对象。
您还可以在一个 for 循环中遍历两个以上的可迭代对象。考虑以下示例,它具有三个输入迭代:

>>> letters = ['a', 'b', 'c']
>>> numbers = [0, 1, 2]
>>> operators = ['*', '/', '+']
>>> for l, n, o in zip(letters, numbers, operators):
...     print(f'Letter: {l}')
...     print(f'Number: {n}')
...     print(f'Operator: {o}')
...
Letter: a
Number: 0
Operator: *
Letter: b
Number: 1
Operator: /
Letter: c
Number: 2
Operator: +
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

并行遍历字典

在 Python 3.6 及更高版本中,字典是有序集合,这意味着它们将元素保持与引入时相同的顺序。如果您利用此功能,则可以使用 Python zip() 函数以安全且连贯的方式遍历多个字典:

>>> dict_one = {'name': 'John', 'last_name': 'Doe', 'job': 'Python Consultant'}
>>> dict_two = {'name': 'Jane', 'last_name': 'Doe', 'job': 'Community Manager'}
>>> for (k1, v1), (k2, v2) in zip(dict_one.items(), dict_two.items()):
...     print(k1, '->', v1)
...     print(k2, '->', v2)
...
name -> John
name -> Jane
last_name -> Doe
last_name -> Doe
job -> Python Consultant
job -> Community Manager
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

解压缩序列

有一个问题经常出现在新 Pythonistas 的论坛中:“如果有一个 zip() 函数,那么为什么没有 unzip() 函数做相反的事情?”
Python 中之所以没有 unzip() 函数,是因为 zip() 的反义词是……嗯,就是 zip()。你还记得 Python zip() 函数就像真正的拉链一样工作吗?到目前为止的示例已经向您展示了 Python 是如何关闭的。那么,如何解压缩 Python 对象呢?
假设您有一个元组列表,并且希望将每个元组的元素分成独立的序列。为此,您可以将 zip() 与解包运算符 * 一起使用,如下所示:

>>> pairs = [(1, 'a'), (2, 'b'), (3, 'c'), (4, 'd')]
>>> numbers, letters = zip(*pairs)
>>> numbers
(1, 2, 3, 4)
>>> letters
('a', 'b', 'c', 'd')
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

在这里,您有一个包含某种混合数据的元组列表。然后,您使用解包运算符 * 解压缩数据,创建两个不同的列表(数字和字母)。

并行排序

排序是编程中的常见操作。假设您想合并两个列表并同时对它们进行排序。为此,您可以将 zip() 与 .sort() 一起使用,如下所示:

>>> letters = ['b', 'a', 'd', 'c']
>>> numbers = [2, 4, 3, 1]
>>> data1 = list(zip(letters, numbers))
>>> data1
[('b', 2), ('a', 4), ('d', 3), ('c', 1)]
>>> data1.sort()  # Sort by letters
>>> data1
[('a', 4), ('b', 2), ('c', 1), ('d', 3)]
>>> data2 = list(zip(numbers, letters))
>>> data2
[(2, 'b'), (4, 'a'), (3, 'd'), (1, 'c')]
>>> data2.sort()  # Sort by numbers
>>> data2
[(1, 'c'), (2, 'b'), (3, 'd'), (4, 'a')]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

在此示例中,您首先使用 zip() 组合两个列表并对其进行排序。请注意 data1 如何按字母排序,而 data2 如何按数字排序。
您还可以同时使用 sorted() 和 zip() 来实现类似的结果:

>>> letters = ['b', 'a', 'd', 'c']
>>> numbers = [2, 4, 3, 1]
>>> data = sorted(zip(letters, numbers))  # Sort by letters
>>> data
[('a', 4), ('b', 2), ('c', 1), ('d', 3)]
  • 1
  • 2
  • 3
  • 4
  • 5

在这种情况下, sorted() 会运行 zip() 生成的迭代器,并按字母对项目进行排序,这一切都在一次完成。这种方法可能会快一点,因为您只需要两个函数调用:zip() 和 sorted()。
使用 sorted(),您还可以编写更通用的代码。这将允许您对任何类型的序列进行排序,而不仅仅是列表。

成对计算

您可以使用 Python zip() 函数进行一些快速计算。假设您在电子表格中有以下数据:在这里插入图片描述
您将使用这些数据来计算您的每月利润。 zip() 可以为您提供一种快速的计算方法:

>>> total_sales = [52000.00, 51000.00, 48000.00]
>>> prod_cost = [46800.00, 45900.00, 43200.00]
>>> for sales, costs in zip(total_sales, prod_cost):
...     profit = sales - costs
...     print(f'Total profit: {profit}')
...
Total profit: 5200.0
Total profit: 5100.0
Total profit: 4800.0
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

建立字典

Python 的字典是一种非常有用的数据结构。有时,您可能需要从两个不同但密切相关的序列构建字典。实现此目的的一种便捷方法是将 dict() 和 zip() 一起使用。例如,假设您从表单或数据库中检索了一个人的数据。现在您有以下数据列表:

>>> fields = ['name', 'last_name', 'age', 'job']
>>> values = ['John', 'Doe', '45', 'Python Developer']
  • 1
  • 2

使用此数据,您需要创建一个字典以进行进一步处理。在这种情况下,您可以将 dict() 与 zip() 一起使用,如下所示:

>>> a_dict = dict(zip(fields, values))
>>> a_dict
{'name': 'John', 'last_name': 'Doe', 'age': '45', 'job': 'Python Developer'}
  • 1
  • 2
  • 3

参考文章:https://realpython.com/python-zip-function/