如何用Python的zip将数据切分为等长分组?该实现是否可靠?
用zip切分可迭代对象为等长分组的原理与可靠性
先看PEP里给出的示例代码(修正了原代码中x = range(n ** 2),的多余逗号):
>>> n = 3 >>> x = range(n ** 2) >>> xn = list(zip(*[iter(x)] * n))
实现原理
这个写法的核心是利用了迭代器的有状态特性和列表乘法的引用特性:
iter(x)生成x对应的迭代器对象,迭代器的关键特点是:每次调用next()方法会推进到下一个元素,且内部状态会持续保留,不会重置。[iter(x)] * n创建的不是n个独立的迭代器,而是一个包含n个同一迭代器引用的列表。也就是说列表里的所有元素都指向同一个迭代器实例。zip(*...)对列表进行解包,相当于把这n个"迭代器"(实际是同一个)作为zip的参数。zip的工作逻辑是:每次循环从每个传入的可迭代对象中取一个元素,组成元组;直到其中某个可迭代对象耗尽。
因为所有传入zip的都是同一个迭代器,所以第一次循环会依次调用这个迭代器的next()三次,取出原可迭代对象的前3个元素组成元组;第二次循环再调用三次,取出接下来的3个元素,以此类推,直到迭代器耗尽,最终得到等长分组的结果。
你自己写的复现代码完全还原了这个逻辑:
n = 3 x = range(n ** 2) xn = [iter(x)] * n res = [] while True: try: col = [] for element in xn: col.append(next(element)) res.append(col) except: break
这段代码手动模拟了zip的执行流程,和示例代码的效果完全一致。
可靠性说明
这个实现完全可靠,并非依赖Python的实现细节:
- 迭代器的状态保留是Python语言规范明确规定的特性,不是某个版本的临时实现。
- zip的行为也是语言规范定义的:从每个输入可迭代对象按顺序取元素,直到任一可迭代对象耗尽。
这个写法是Python社区广泛认可的经典技巧,可安全用于切分任何可迭代对象(包括生成器、range等惰性序列),只要原可迭代对象的长度是n的整数倍,就能得到完整的等长分组;如果长度不是n的整数倍,最后不足n个的元素会被丢弃(如果需要保留最后一组,可以改用itertools.zip_longest)。
内容的提问来源于stack exchange,提问作者Campbell Hutcheson
相关产品推荐
相关产品推荐

