for 循环中哪个更符合 pythonic:zip 还是枚举?
Which is more pythonic in a for loop: zip or enumerate?
考虑到可扩展性和可读性,其中哪一个被认为更像 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)
我问的原因是我一直在混合使用两者。我应该坚持一种标准方法,但它应该是哪种?
毫无疑问,zip
更像 pythonic。它不需要您使用变量来存储索引(否则您不需要),并且使用它可以统一处理列表,而使用 enumerate
,您可以迭代一个列表,并为其他列表,即非统一处理。
但是,您应该注意 zip
仅运行到两个列表中较短的一个。为了避免重复别人的回答,我只在此处包含一个参考:someone else's answer.
@user3100115 恰当地指出,在 python2 中,您应该更喜欢使用 itertools.izip
而不是 zip
,因为它具有惰性(更快且内存效率更高)。在 python3 zip
中已经表现得像 py2 的 izip
.
zip 更像 pythonic,因为你不需要另一个变量,同时你也可以使用
from collections import deque
deque(map(lambda x, y:sys.stdout.write(x+" "+y+"\n"),group,tag),maxlen=0)
由于我们在此处打印输出,因此需要更正 None 个值的列表,并且前提是您的列表长度相同。
Update :好吧,在这种情况下,它可能不太好,因为您正在打印组和标签值,它会生成一个 None 值列表,因为 sys.stdout.write 但实际上,如果您需要获取值,那会更好。
您标题 "Which is more pythonic; zip or enumerate...?" 中提出的问题的答案是:它们都是。 enumerate
只是 zip
.
的特例
关于 that 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
类collection中的标签[]
索引。这无缘无故地排除了大量的可迭代对象,例如文件流和生成器。
为什么一个参数比另一个参数受到更严格的约束?该限制不是用户试图解决的问题所固有的,因为生成器可以很容易地以另一种方式编写:
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
,您必须以某种方式捕捉或阻止这种情况.当您的循环同时枚举和访问相同的 list-like collection 时,这通常不是问题,但在这里您枚举一个,然后从另一个访问项目。您必须添加 更多 代码来处理不可能在基于 zip
的版本中发生 的错误。
避免不必要的 idx
变量也很好,但这并不是两种方法之间的决定性区别。
有关可迭代对象、生成器和使用它们的函数的更多信息,请参阅 Ned Batchelder 的 PyCon US 2013 演讲,“Loop Like a Native" (text, 30-minute video”。
虽然其他人指出 zip
实际上比 enumerate
更 pythonic,但我来这里是为了看看它是否更有效。根据我的测试,当简单地并行访问和使用多个列表中的项目时,zip
比 enumerate
快 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")
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]
这是一个简单的起始问题。我认为 range(len([list])) 不是 pythonic 尝试非 pythonist 解决方案。
考虑并阅读优秀的 python 文档,我真的很喜欢 docs 作为简单 pythonic 代码中的 numpy 格式样式,如果您需要 for 循环,枚举是可迭代的解决方案,因为make an iterable是一种综合形式。
list_a = ['a', 'b', 'c'];
list_2 = ['1', '2', '3',]
[print(a) for a in lista]
用于执行可打印行,也许更好的是生成器,
item = genetator_item = (print(i, a) for i, a in enumerate(lista) if a.find('a') == 0)
next(item)
对于多行 for 和更复杂的 for 循环,我们可以使用 enumerate(zip(.
for i, (arg1, arg2) i in enumerate(zip(list_a, list_2)):
print('multiline') # do complex code
但也许在扩展的 pythonic 代码中,我们可以使用带有 itertools 的另一种复杂格式,注意 len(list_a[:]) slice
最后的 idx
from itertools import count as idx
for arg1, arg2, i in zip(list_a, list_2, idx(start=1)):
print(f'multiline {i}: {arg1}, {arg2}') # do complex code
考虑到可扩展性和可读性,其中哪一个被认为更像 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)
我问的原因是我一直在混合使用两者。我应该坚持一种标准方法,但它应该是哪种?
毫无疑问,zip
更像 pythonic。它不需要您使用变量来存储索引(否则您不需要),并且使用它可以统一处理列表,而使用 enumerate
,您可以迭代一个列表,并为其他列表,即非统一处理。
但是,您应该注意 zip
仅运行到两个列表中较短的一个。为了避免重复别人的回答,我只在此处包含一个参考:someone else's answer.
@user3100115 恰当地指出,在 python2 中,您应该更喜欢使用 itertools.izip
而不是 zip
,因为它具有惰性(更快且内存效率更高)。在 python3 zip
中已经表现得像 py2 的 izip
.
zip 更像 pythonic,因为你不需要另一个变量,同时你也可以使用
from collections import deque
deque(map(lambda x, y:sys.stdout.write(x+" "+y+"\n"),group,tag),maxlen=0)
由于我们在此处打印输出,因此需要更正 None 个值的列表,并且前提是您的列表长度相同。
Update :好吧,在这种情况下,它可能不太好,因为您正在打印组和标签值,它会生成一个 None 值列表,因为 sys.stdout.write 但实际上,如果您需要获取值,那会更好。
您标题 "Which is more pythonic; zip or enumerate...?" 中提出的问题的答案是:它们都是。 enumerate
只是 zip
.
关于 that 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
类collection中的标签[]
索引。这无缘无故地排除了大量的可迭代对象,例如文件流和生成器。
为什么一个参数比另一个参数受到更严格的约束?该限制不是用户试图解决的问题所固有的,因为生成器可以很容易地以另一种方式编写:
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
,您必须以某种方式捕捉或阻止这种情况.当您的循环同时枚举和访问相同的 list-like collection 时,这通常不是问题,但在这里您枚举一个,然后从另一个访问项目。您必须添加 更多 代码来处理不可能在基于 zip
的版本中发生 的错误。
避免不必要的 idx
变量也很好,但这并不是两种方法之间的决定性区别。
有关可迭代对象、生成器和使用它们的函数的更多信息,请参阅 Ned Batchelder 的 PyCon US 2013 演讲,“Loop Like a Native" (text, 30-minute video”。
虽然其他人指出 zip
实际上比 enumerate
更 pythonic,但我来这里是为了看看它是否更有效。根据我的测试,当简单地并行访问和使用多个列表中的项目时,zip
比 enumerate
快 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")
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]
这是一个简单的起始问题。我认为 range(len([list])) 不是 pythonic 尝试非 pythonist 解决方案。
考虑并阅读优秀的 python 文档,我真的很喜欢 docs 作为简单 pythonic 代码中的 numpy 格式样式,如果您需要 for 循环,枚举是可迭代的解决方案,因为make an iterable是一种综合形式。
list_a = ['a', 'b', 'c'];
list_2 = ['1', '2', '3',]
[print(a) for a in lista]
用于执行可打印行,也许更好的是生成器,
item = genetator_item = (print(i, a) for i, a in enumerate(lista) if a.find('a') == 0)
next(item)
对于多行 for 和更复杂的 for 循环,我们可以使用 enumerate(zip(.
for i, (arg1, arg2) i in enumerate(zip(list_a, list_2)):
print('multiline') # do complex code
但也许在扩展的 pythonic 代码中,我们可以使用带有 itertools 的另一种复杂格式,注意 len(list_a[:]) slice
最后的 idxfrom itertools import count as idx
for arg1, arg2, i in zip(list_a, list_2, idx(start=1)):
print(f'multiline {i}: {arg1}, {arg2}') # do complex code