Python `zip()` 函数使用教程:掌握配对迭代 – wiki基地


Python zip() 函数使用教程:掌握配对迭代的艺术

在 Python 的世界里,处理多个序列(如列表、元组等)是常见的任务。有时,我们需要将这些序列中的元素按照它们的顺序一一配对,然后进行联合处理。想象一下,你有两列数据,一列是姓名,一列是年龄,你想要将每个姓名和对应的年龄关联起来。这时,Python 的内置函数 zip() 就派上了用场。

zip() 函数是 Python 中一个极其强大且优雅的工具,它能够将多个可迭代对象打包成一个元组的迭代器,其中每个元组都包含来自每个可迭代对象的相应位置上的元素。就像拉链(zipper)一样,它将两边的齿合在一起,形成一个整体。掌握 zip() 函数,意味着你掌握了高效处理配对数据的能力,这在数据处理、循环控制等许多场景中都至关重要。

本文将带你深入了解 zip() 函数的各个方面,包括:

  1. zip() 函数的基本用法
  2. zip() 函数的返回值类型
  3. 处理不同长度的可迭代对象
  4. 使用 zip() 函数进行迭代
  5. 如何“解压” zip() 对象
  6. zip() 函数与其他 Python 特性的结合使用(如列表推导、字典创建)
  7. zip() 函数的常见应用场景
  8. 使用 itertools.zip_longest 处理不同长度序列的另一种方式
  9. 使用 zip() 时需要注意的事项

让我们开始这段掌握配对迭代的旅程吧!

1. zip() 函数的基本用法

zip() 函数接受任意数量的可迭代对象作为参数,并返回一个迭代器。这个迭代器的每个元素都是一个元组,元组的第 i 个元素来自于第 i 个输入可迭代对象的相应位置。

最常见的用法是将两个或多个列表、元组等可迭代对象压缩在一起:

“`python

示例 1.1:压缩两个列表

names = [‘Alice’, ‘Bob’, ‘Charlie’]
ages = [25, 30, 35]

使用 zip 函数进行配对

zipped_data = zip(names, ages)

打印 zip 对象本身 (你会看到一个迭代器对象)

print(zipped_data) # 输出类似:

要查看配对后的结果,通常需要将其转换为列表或迭代遍历它

转换为列表

list_zipped_data = list(zipped_data)
print(list_zipped_data)

输出:[(‘Alice’, 25), (‘Bob’, 30), (‘Charlie’, 35)]

示例 1.2:压缩三个元组

colors = (‘red’, ‘green’, ‘blue’)
codes = (‘#FF0000’, ‘#00FF00’, ‘#0000FF’)
types = (‘primary’, ‘primary’, ‘primary’)

zipped_colors = zip(colors, codes, types)
list_zipped_colors = list(zipped_colors)
print(list_zipped_colors)

输出:[(‘red’, ‘#FF0000’, ‘primary’), (‘green’, ‘#00FF00’, ‘primary’), (‘blue’, ‘#0000FF’, ‘primary’)]

示例 1.3:压缩字符串和列表

letters = ‘abc’
numbers = [1, 2, 3]
zipped_mixed = zip(letters, numbers)
list_zipped_mixed = list(zipped_mixed)
print(list_zipped_mixed)

输出:[(‘a’, 1), (‘b’, 2), (‘c’, 3)]

“`

可以看到,zip() 函数非常直观。它就像对准了多个跑道的运动员,发令枪响后,每个跑道的第一个运动员一起出发,形成第一个元组;接着是第二个,以此类推。

2. zip() 函数的返回值类型:迭代器

理解 zip() 函数返回的是一个迭代器(iterator)非常重要。迭代器是一种惰性计算的对象,它在每次请求下一个元素时才计算并返回该元素。这意味着 zip() 函数本身并不会立即创建所有配对好的元组,而是等待你通过某种方式(如使用 list() 转换或在 for 循环中迭代)来获取它们。

“`python
names = [‘Alice’, ‘Bob’, ‘Charlie’]
ages = [25, 30, 35]

zipped_data = zip(names, ages)

第一次使用:迭代并打印

print(“第一次迭代:”)
for item in zipped_data:
print(item)

输出:

(‘Alice’, 25)

(‘Bob’, 30)

(‘Charlie’, 35)

第二次使用:尝试再次迭代或转换为列表

print(“\n第二次尝试转换为列表:”)
list_again = list(zipped_data)
print(list_again)

输出:[]

为什么第二次转换得到空列表?

因为迭代器是一次性的。第一次迭代已经消耗了 zip 对象中的所有元素。

如果需要多次使用配对结果,应该将 zip 对象转换为列表或元组等数据结构后使用。

“`

这种迭代器的特性使得 zip() 在处理大型数据集时非常高效,因为它不会一次性占用大量内存来存储所有配对好的元组。

3. 处理不同长度的可迭代对象:默认行为

zip() 函数处理长度不同的可迭代对象时,它的默认行为是截断(truncate)。它会从最短的可迭代对象耗尽时停止配对。

“`python

示例 3.1:第一个列表较短

numbers = [1, 2, 3]
letters = [‘a’, ‘b’, ‘c’, ‘d’, ‘e’]

zipped_unequal_short = zip(numbers, letters)
list_zipped_unequal_short = list(zipped_unequal_short)
print(list_zipped_unequal_short)

输出:[(1, ‘a’), (2, ‘b’), (3, ‘c’)]

‘d’ 和 ‘e’ 被忽略了,因为 numbers 列表只有 3 个元素。

示例 3.2:第二个列表较短

numbers = [1, 2, 3, 4, 5]
letters = [‘a’, ‘b’, ‘c’]

zipped_unequal_long = zip(numbers, letters)
list_zipped_unequal_long = list(zipped_unequal_long)
print(list_zipped_unequal_long)

输出:[(1, ‘a’), (2, ‘b’), (3, ‘c’)]

4 和 5 被忽略了,因为 letters 列表只有 3 个元素。

“`

这种截断行为在很多场景下是符合预期的,特别是当你只想处理那些在所有序列中都有对应元素的项时。然而,如果你需要保留所有元素,即使某些序列已经耗尽,你需要使用 itertools 模块中的 zip_longest 函数。

4. 使用 itertools.zip_longest 处理不同长度序列(填充缺失值)

itertools.zip_longest 函数是 zip() 的一个变体,它不会在最短序列耗尽时停止,而是继续配对,并在较短的序列中填充一个指定的值(默认为 None),直到最长的序列耗尽。

要使用 zip_longest,你需要先导入 itertools 模块:

“`python
from itertools import zip_longest

示例 4.1:使用 zip_longest

numbers = [1, 2, 3]
letters = [‘a’, ‘b’, ‘c’, ‘d’, ‘e’]

zipped_longest = zip_longest(numbers, letters)
list_zipped_longest = list(zipped_longest)
print(list_zipped_longest)

输出:[(1, ‘a’), (2, ‘b’), (3, ‘c’), (None, ‘d’), (None, ‘e’)]

注意,numbers 中没有对应 ‘d’ 和 ‘e’ 的元素,所以用 None 填充。

示例 4.2:使用 fillvalue 参数指定填充值

zipped_longest_fill = zip_longest(numbers, letters, fillvalue=’-‘)
list_zipped_longest_fill = list(zipped_longest_fill)
print(list_zipped_longest_fill)

输出:[(1, ‘a’), (2, ‘b’), (3, ‘c’), (‘-‘, ‘d’), (‘-‘, ‘e’)]

“`

zip_longest 提供了更大的灵活性,特别是当你需要处理可能缺失对应关系的数据时。

5. 使用 zip() 函数进行迭代

zip() 函数最常见的用途是将配对好的元素直接用于 for 循环中进行迭代处理。这比先转换为列表再迭代更高效,因为它充分利用了迭代器的惰性特性。

for 循环中,你可以使用元组拆包(tuple unpacking)来直接获取每个配对元组中的元素:

“`python
names = [‘Alice’, ‘Bob’, ‘Charlie’]
ages = [25, 30, 35]

print(“使用 zip 在 for 循环中迭代:”)
for name, age in zip(names, ages):
print(f”{name} is {age} years old.”)

输出:

Alice is 25 years old.

Bob is 30 years old.

Charlie is 35 years old.

示例 5.2:结合 enumerate 和 zip

有时你不仅需要配对的元素,还需要它们的索引

for index, (name, age) in enumerate(zip(names, ages)):
print(f”Item {index}: {name} is {age} years old.”)

输出:

Item 0: Alice is 25 years old.

Item 1: Bob is 30 years old.

Item 2: Charlie is 35 years old.

注意:这里需要两层解包,外层解包 enumerate 的 (index, (name, age)),内层解包 zip 的 (name, age)。

“`

这种迭代方式清晰地表达了你的意图:同时遍历并处理来自多个序列的对应元素。

6. 如何“解压” zip() 对象

除了将多个序列“压缩”在一起,zip() 函数还可以用来“解压”一个已经“压缩”好的 zip 对象(或者更常见的是,一个由 zip 创建并转换为列表或元组的数据结构)。这里的“解压”实际上是一种矩阵转置的操作:将原来的列变成行,行变成列。

要实现“解压”,我们可以利用 Python 的 * 操作符。当 * 操作符应用于一个可迭代对象(如列表或元组)时,它会将这个可迭代对象中的每个元素作为单独的参数传递给函数。

“`python

示例 6.1:先 zip,再 unzip

names = [‘Alice’, ‘Bob’, ‘Charlie’]
ages = [25, 30, 35]

压缩数据并转换为列表

zipped_list = list(zip(names, ages))
print(“压缩后的列表:”, zipped_list)

输出:压缩后的列表: [(‘Alice’, 25), (‘Bob’, 30), (‘Charlie’, 35)]

解压

zipped_list 是 [(‘Alice’, 25), (‘Bob’, 30), (‘Charlie’, 35)]

*zipped_list 会将其展开为 (‘Alice’, 25), (‘Bob’, 30), (‘Charlie’, 35) 这三个独立的参数

然后 zip 函数会将这三个参数作为输入序列进行压缩

unzipped_names, unzipped_ages = zip(*zipped_list)

解压的结果仍然是 zip 对象,通常需要转换为列表或元组

unzipped_names = list(unzipped_names)
unzipped_ages = list(unzipped_ages)

print(“解压后的姓名列表:”, unzipped_names)

输出:解压后的姓名列表: [‘Alice’, ‘Bob’, ‘Charlie’]

print(“解压后的年龄列表:”, unzipped_ages)

输出:解压后的年龄列表: [25, 30, 35]

“`

这个“解压”技巧非常有用,例如,当你从某个源获取了一系列元组组成的列表(每行是一个记录,每列是一个字段),然后想把某个“列”提取出来作为一个独立的列表。

需要注意的是,解压操作要求原来的压缩结果中的每个内部元组(或列表)具有相同的长度。如果长度不一致(例如使用了 zip_longest 并且存在填充值),解压的结果可能会和你期望的不同。

7. zip() 函数与其他 Python 特性的结合使用

zip() 函数经常与其他 Python 特性结合使用,以完成更简洁和高效的代码。

  • 列表推导式 (List Comprehensions): 结合 zip() 和列表推导式可以非常方便地根据多个序列创建新的列表。

    “`python
    numbers = [1, 2, 3]
    letters = [‘a’, ‘b’, ‘c’]

    创建一个新的列表,元素是字符串,格式如 “1-a”

    combined_list = [f”{num}-{letter}” for num, letter in zip(numbers, letters)]
    print(combined_list)

    输出:[‘1-a’, ‘2-b’, ‘3-c’]

    创建一个列表,包含每个数字和其平方的元组

    numbers = [1, 2, 3, 4, 5]
    squares = [n**2 for n in numbers] # 先计算平方列表
    num_square_pairs = list(zip(numbers, squares)) # 再打包
    print(num_square_pairs)

    输出:[(1, 1), (2, 4), (3, 9), (4, 16), (5, 25)]

    另一种方式:直接在一个推导式中完成

    (这其实不是 zip 的典型应用,但展示了如何处理相关数据)

    “`

  • 字典创建 (Dictionary Creation): zip() 函数是根据两个列表(一个用于键,一个用于值)创建字典的常见且优雅的方式。

    “`python
    keys = [‘name’, ‘age’, ‘city’]
    values = [‘Alice’, 25, ‘New York’]

    使用 zip 和 dict() 函数创建字典

    person_dict = dict(zip(keys, values))
    print(person_dict)

    输出:{‘name’: ‘Alice’, ‘age’: 25, ‘city’: ‘New York’}

    如果键和值列表长度不同,dict() 会因为 zip 的截断行为而只包含最短列表长度的键值对

    keys_short = [‘name’, ‘age’]
    person_dict_short = dict(zip(keys_short, values))
    print(person_dict_short)

    输出:{‘name’: ‘Alice’, ‘age’: 25}

    “`

8. zip() 函数的常见应用场景

zip() 函数的实用性体现在许多不同的编程场景中:

  • 同时遍历多个列表/元组:这是最基础的应用,例如同时处理学生的姓名和分数列表。
  • 将两列数据组合成键值对:如上所示,创建字典。
  • 数据清洗和转换:将来自不同源但相关的数据对齐。
  • 矩阵或二维数据的转置:使用解压技巧实现。
  • 同时更新多个变量:虽然不常用,但理论上可以用 a, b = zip(iter_a, iter_b).__next__() 来一次获取多个迭代器的下一个元素。
  • 算法实现:在需要同时考虑多个序列中对应元素位置的算法中(如某些排序、合并操作的辅助步骤)。

9. 使用 zip() 时需要注意的事项

尽管 zip() 函数非常方便,但在使用时也需要注意一些细节:

  • 返回值是迭代器: 谨记 zip() 返回的是一个迭代器,它只能被消耗一次。如果你需要多次使用结果,请将其转换为列表、元组或其他数据结构。
  • 不同长度的处理: 默认行为是截断到最短序列的长度。如果你的业务逻辑需要保留所有元素,请使用 itertools.zip_longest
  • 内存使用: 由于返回的是迭代器,zip() 在处理大型数据集时通常是内存高效的。只有当你将迭代器转换为列表等完整结构时,才会占用与数据量成比例的内存。
  • 解压的限制: 使用 *zip(...) 进行解压时,要求原始压缩后的内部元组(或列表)长度一致。
  • 可迭代对象的类型: zip() 可以接受任何可迭代对象,包括自定义的可迭代对象、文件对象、生成器等。

总结

Python 的 zip() 函数是一个优雅且强大的工具,它使得将多个可迭代对象的元素按位置配对变得异常简单。通过返回一个迭代器,它在处理大型数据集时保持了高效的内存利用。无论是简单地同时遍历两个列表,还是将两列数据转换为字典,抑或是执行数据的转置,zip() 函数都能提供简洁明了的解决方案。

理解 zip() 的迭代器特性和处理不同长度序列的默认行为是高效使用的关键。同时,结合 itertools.zip_longest 可以处理更复杂的场景。掌握 zip() 函数,无疑将提升你在 Python 中处理序列数据的能力,让你的代码更加简洁、高效和富有表现力。

现在,你已经详细了解了 zip() 函数的各个方面。多加练习,尝试在你的代码中应用它,你很快就能熟练掌握这个强大的配对迭代工具了!


发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部