在for循环中哪个更pythonic:zip或enumerate?

时间:2015-11-27 10:52:59

标签: python for-loop iterable

考虑到可扩展性和可读性,其中哪一个被认为是pythonic? 使用enumerate

group = ['A','B','C']
tag = ['a','b','c']

for idx, x in enumerate(group):
    print(x, tag[idx])

或使用zip

for x, y in zip(group, tag):
    print(x, y)

我问的原因是我一直在混合两者。我应该采用一种标准方法,但它应该是什么?

5 个答案:

答案 0 :(得分:7)

毫无疑问,zip更像是pythonic。它并不要求您使用变量来存储索引(否则您不需要),并且使用它可以统一处理列表,而使用enumerate时,您可以迭代一个列表,并索引另一个列表,即非统一处理。

但是,您应该注意zip仅运行两个列表中较短的一个的警告。为避免重复别人的回答,我在这里只提供一个引用:someone else's answer

@ user3100115恰当地指出在python2中,你应该更喜欢使用itertools.izip而不是zip,因为它的懒惰性质(更快,内存效率更高)。在python3中zip已经像py2' s izip一样。

答案 1 :(得分:2)

你的标题中提到的问题的答案,"哪个更pythonic;拉链或枚举......?"是:他们都是。 enumerate只是zip的一个特例。

关于 for循环的更具体问题的答案是:使用zip,但不是出于您目前所见的原因。

该循环中zip的最大优势与zip本身无关。它与避免在enumerate循环中做出的假设有关。为了解释,我将基于你的两个例子制作两个不同的发生器:

def process_items_and_tags(items, tags):
    "Do something with two iterables: items and tags."
    for item, tag in zip(items, tag):
        yield process(item, tag)

def process_items_and_list_of_tags(items, tags_list):
    "Do something with an iterable of items and an indexable collection of tags."
    for idx, item in enumerate(items):
        yield process(item, tags_list[idx])

两个生成器都可以将任何iterable作为它们的第一个参数(items),但它们处理第二个参数的方式不同。 基于enumerate的方法只能 处理list - 类似于[]索引的集合中的处理代码。这排除了大量的迭代,比如文件流和生成器,没有充分的理由。

为什么一个参数比另一个参数受到更严格的限制?在用户试图解决的问题中,限制并不是固有的,因为生成器可以很容易地用另一种方式编写:#/ p>

def process_list_of_items_and_tags(items_list, tags):
    "Do something with an indexable collection of items and an iterable of tags."
    for idx, tag in enumerate(tags):
        yield process(items[idx], tag)

同样的结果,对输入的不同限制。为什么你的来电者必须知道或关心这些?

作为一个额外的惩罚,some_list[some_index] 形式的任何内容都可能会引发IndexError ,您必须以某种方式捕获或阻止它。当你的循环枚举并访问相同的类似列表的集合时,这通常不是问题,但是在这里你要枚举一个然后从另一个访问项目。您必须添加更多代码才能处理基于zip的版本中无法发生的错误。

避免不必要的idx变量也很不错,但这两种方法之间几乎没有决定性差异。

有关使用它们的可迭代,生成器和函数主题的更多信息,请参阅Ned Batchelder的PyCon US 2013演讲," Loop Like a Native" (text30-minute video)。

答案 2 :(得分:1)

尽管其他人指出zip实际上比enumerate更具有Python风格,但我还是来这里看看它是否更有效。根据我的测试,在简单地并行访问和使用多个列表中的项目时,zipenumerate快10%到20%。

在这里,我有三个(相同)递增长度的列表被并行访问。当列表的长度超过几个项目时,zip /枚举的时间比率小于零,并且zip更快。

Graphed in R-Studio

我使用的代码:

import timeit

setup = \
"""
import random
size = {}
a = [ random.randint(0,i+1) for i in range(size) ]
b = [ random.random()*i for i in range(size) ]
c = [ random.random()+i for i in range(size) ]
"""
code_zip = \
"""
data = []
for x,y,z in zip(a,b,c):
    data.append(x+z+y)
"""
code_enum = \
"""
data = []
for i,x in enumerate(a):
    data.append(x+c[i]+b[i])
"""
runs = 10000
sizes = [ 2**i for i in range(16) ]
data = []

for size in sizes:
    formatted_setup = setup.format(size)
    time_zip = timeit.timeit(code_zip, formatted_setup, number=runs)
    time_enum = timeit.timeit(code_enum, formatted_setup, number=runs)
    ratio = time_zip/time_enum
    row = (size,time_zip,time_enum,ratio)
    data.append(row)

with open("testzipspeed.csv", 'w') as csv_file:
    csv_file.write("size,time_zip,time_enumerate,ratio\n")

    for row in data:
        csv_file.write(",".join([ str(i) for i in row ])+"\n")

答案 3 :(得分:0)

zip更像pythonic所说的你不需要另一个变量,而你也可以使用

from collections import deque
deque(map(lambda x, y:sys.stdout.write(x+" "+y+"\n"),group,tag),maxlen=0)

由于我们在这里打印输出,因此需要纠正无值的列表,并且还提供了相同长度的列表。

更新:在这种情况下它可能不太好,因为您正在打印组和标记值,并且由于sys.stdout.write而生成一个None值列表但实际上如果您需要获取值会更好。

答案 4 :(得分:0)

zip可能更像Python,但是有一个陷阱。如果要更改元素,则需要使用索引。遍历元素将不起作用。例如:

x = [1,2,3]
for elem in x:
    elem *= 10
print(x)

输出:[1,2,3]

y = [1,2,3]
for index in range(len(y)):
    y[i] *= 10
print(y)

输出:[10,20,30]