编程后端开发工程师

Python中内置函数zip()是如何工作的,它的用途是什么,处理不同长度序列时有哪些注意事项?

用 Hintsage AI 助手通过面试

答案。

问题的历史

zip()函数最早出现在Python 2(当时返回一个列表),从Python 3开始返回惰性迭代器。它按元素将多个序列“拉链”在一起,便于有效地处理并行可迭代集合。

问题

通常需要同时处理多个列表(或其他类型的序列)——例如遍历键值对或处理点对的坐标。独立同步索引会导致错误和代码的可读性下降,尤其是对于不同长度的集合。

解决方案

zip()函数接受任意数量的可迭代对象,并返回一个元组迭代器,每个元组中包含对应的可迭代对象的元素。如果序列长度不同,结果将在最短的地方中断。

代码示例:

names = ['Alice', 'Bob', 'Charlie'] ages = [24, 27, 30] for name, age in zip(names, ages): print(f'{name} is {age} years old')

可以使用*展开zip:

pairs = [(1, 'a'), (2, 'b'), (3, 'c')] nums, chars = zip(*pairs) print(nums) # (1, 2, 3) print(chars) # ('a', 'b', 'c')

关键特点:

  • zip()返回一个迭代器(在Python 3中),而不是列表。
  • zip()的工作在最短的可迭代对象上中断。
  • 允许无需显式控制索引的并行集合处理。

陷阱问题。

如果将不同长度的集合传递给zip()会发生什么?

zip()会在到达最短集合的末尾时停止——其他较长集合的元素会被忽略。

print(list(zip([1,2,3], ['a','b']))) # [(1, 'a'), (2, 'b')]

如何得到元组,补充较短的序列为默认值?

标准的zip()不会这样做,但可以使用itertools.zip_longest实现此行为:

from itertools import zip_longest for a, b in zip_longest([1,2], ['x','y','z'], fillvalue=None): print(a, b) # 1 x # 2 y # None z

可以“解包”zip()的结果以重新获得原始列表吗?

可以,如果所有原始集合的长度相同且结果未被修改,操作符*可以展开zip。

pairs = [(1,2), (3,4)] a, b = zip(*pairs) print(a) # (1, 3) print(b) # (2, 4)

常见错误和反模式

  • 期待zip()总是“到达”最长集合的末尾。
  • 假设在Python 3中zip()返回列表(这是一个迭代器,有时需要包裹在list()中)。
  • 在可变源上使用zip,这会在每次迭代中消耗它们。

生活中的例子

负面案例

处理不同长度的关联集合而不考虑zip的特性:

lst1 = [1,2,3,4] lst2 = ['a','b'] for x, y in zip(lst1, lst2): print(x, y) # 1 a # 2 b # (3,4)和'l','c'没有被处理

优点:

  • 如果序列长度一致,简单明了。

缺点:

  • 如果集合的实际长度不同,可能会丢失值。

正面案例

使用zip_longest与fillvalue,以确保不丢失任何元素:

from itertools import zip_longest lst1 = [1,2,3,4] lst2 = ['a','b'] for x, y in zip_longest(lst1, lst2, fillvalue='?'): print(x, y) # 1 a # 2 b # 3 ? # 4 ?

优点:

  • 确保处理所有元素。
  • 可以显式设置“空”值。

缺点:

  • 需要导入外部模块。
  • 重要的是不要忘记fillvalue,否则默认值为None。