在 for 循环中哪个更 Pythonic:压缩还是枚举?

Posted

技术标签:

【中文标题】在 for 循环中哪个更 Pythonic:压缩还是枚举?【英文标题】:Which is more pythonic in a for loop: zip or enumerate? 【发布时间】:2016-03-01 13:29:48 【问题描述】:

考虑到可扩展性和可读性,其中哪一个被认为更具 Python 风格? 使用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)

我问的原因是我一直在使用两者的混合。我应该保持一种标准方法,但应该采用哪种方法?

【问题讨论】:

zip 专为此类任务而设计。你的任务是遍历每一对,而不是遍历数字。而 Python 语法正好可以做到这一点。 是的。 zip 更符合 Python 风格。 如果列表长度相同,您甚至可以使用map(lambda x, y:sys.stdout.write(x+" "+y+"\n"),group,tag) 【参考方案1】:

毫无疑问,zip 更符合 Python 风格。它不需要您使用变量来存储索引(否则您不需要),并且使用它可以统一处理列表,而使用enumerate,您可以遍历一个列表,并索引另一个列表,即非统一处理。

但是,您应该注意zip 只能运行到两个列表中较短的那个。为避免重复其他人的答案,我只在此处提供参考:someone else's answer。

@user3100115 恰当地指出,在 python2 中,您应该更喜欢使用 itertools.izip 而不是 zip,因为它具有惰性(更快且内存效率更高)。在 python3 中,zip 已经表现得像 py2 的 izip

【讨论】:

【参考方案2】:

虽然其他人指出 zip 实际上比 enumerate 更 Pythonic,但我来这里看看它是否更有效率。根据我的测试,当简单地并行访问和使用多个列表中的项目时,zipenumerate 快大约 10% 到 20%。

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

我使用的代码:

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】:

你的标题中提出的问题的答案,“哪个更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])

两个生成器都可以将任何可迭代对象作为它们的第一个参数 (items),但它们处理第二个参数的方式不同。 基于enumerate 的方法只能处理带有[] 索引的list 类集合中的标签。这无缘无故地排除了大量的可迭代对象,例如文件流和生成器。

为什么一个参数的约束比另一个参数更严格?限制不是用户试图解决的问题所固有的,因为生成器可以很容易地以相反的方式编写:

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”(text、30-minute video)。

【讨论】:

另一个答案提到了这一点:“遍历一个列表,并索引另一个列表”。但很高兴突出差异。 @arekolek:确实,accepted answer 中提到过,但更多是出于审美考虑。该答案并没有说[] 表示法限制了所使用的可迭代对象的 type,或者索引引入了新的故障模式。【参考方案4】:

zip 更 Pythonic,因为你不需要另一个变量,而你也可以使用

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

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

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

【讨论】:

怎么比for x, y in zip(group, tag): print(x, y)简单?另请参阅***.com/a/18433519/1916449。 还有Is it Pythonic to use list comprehensions for just side effects?。提示:这个问题还问什么是 Pythonic。 @arekolek 这是 zip https://hg.python.org/cpython/file/57c157be847f/Python/bltinmodule.c 的原生 c 实现,它可能涉及比我猜的地图更多的迭代。 我使用%timeit 在两个列表中测量了ipython 中两种解决方案的时间,每个列表有100 万个随机字母。你的速度快了 3 倍,但这仅仅是因为它使用了 write 而不是 print。在两者中使用write 时,没有任何区别。 不,它并不快。只有write 似乎比print 快。您的解决方案似乎是关于 map 而不是 zip,而不是 write 而不是 print【参考方案5】:

zip 可能更 Pythonic,但它有一个陷阱。如果要就地更改元素,则需要使用索引。迭代元素将不起作用。例如:

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]

【讨论】:

以上是关于在 for 循环中哪个更 Pythonic:压缩还是枚举?的主要内容,如果未能解决你的问题,请参考以下文章

在JavaScript循环语句中,for 和for.in 循环哪个效率更高

c++中for循环和switch语句哪个更高效

大多数 Pythonic for / enumerate 循环?

模块函数 vs 静态方法 vs 类方法 vs 无装饰器:哪个成语更 Pythonic?

是否有一种 Pythonic 的方式来跳过 for 循环中的 if 语句以使我的代码运行得更快?

使用不同过滤器针对同一个列表处理多个 for 循环的 Pythonic 方法?