如何限制Python中循环的迭代次数?

亚伦·霍尔(Aaron Hall):

说我有一个项目列表,并且我想遍历其中的前几个:

items = list(range(10)) # I mean this to represent any kind of iterable.
limit = 5

天真的实现

来自其他语言的Python语言可能会编写以下完美可服务且性能卓越(如果是通用的)代码:

index = 0
for item in items: # Python's `for` loop is a for-each.
    print(item)    # or whatever function of that item.
    index += 1
    if index == limit:
        break

更惯用的实现

但是Python已经枚举了,它很好地包含了大约一半的代码:

for index, item in enumerate(items):
    print(item)
    if index == limit: # There's gotta be a better way.
        break

因此,我们已经将多余的代码减少了一半。但是必须有更好的方法。

我们可以近似以下伪代码行为吗?

我认为,如果枚举采用了另一个可选stop参数(例如,它采用了start这样参数:)enumerate(items, start=1),那将是理想的选择,但以下内容不存在(请参见此处的枚举文档):

# hypothetical code, not implemented:
for _, item in enumerate(items, start=0, stop=limit): # `stop` not implemented
    print(item)

请注意,index由于无需引用它,因此无需命名

是否有惯用的方式编写以上内容?怎么样?

第二个问题:为什么没有内置枚举?

亚伦·霍尔(Aaron Hall):

如何限制Python中循环的迭代次数?

for index, item in enumerate(items):
    print(item)
    if index == limit:
        break

有没有一种简短,惯用的方式写上面的东西?怎么样?

包括索引

zip停止其参数的最短迭代。(与的行为相反zip_longest,后者使用的是最长的可迭代对象。)

range 可以提供有限的可迭代项,我们可以将其与主要可迭代项一起传递给zip。

因此,我们可以将一个range对象(带有其stop参数)传递给zip并像使用有限枚举一样使用它。

zip(range(limit), items)

使用Python 3 ziprange返回可迭代对象,可迭代对象通过管道传递数据,而不是在中间步骤的列表中物化数据。

for index, item in zip(range(limit), items):
    print(index, item)

为了获得在Python 2相同的行为,刚刚替补xrangerangeitertools.izipzip

from itertools import izip
for index, item in izip(xrange(limit), items):
    print(item)

如果不需要索引, itertools.islice

您可以使用itertools.islice

for item in itertools.islice(items, 0, stop):
    print(item)

不需要分配给索引。

组成enumerate(islice(items, stop))获取索引

正如Pablo Ruiz Ruiz指出的那样,我们也可以用枚举组成islice。

for index, item in enumerate(islice(items, limit)):
    print(index, item)

为什么不内置enumerate

这是用纯Python枚举实现的(可能进行了修改以在注释中获得所需的行为):

def enumerate(collection, start=0):  # could add stop=None
    i = start
    it = iter(collection)
    while 1:                         # could modify to `while i != stop:`
        yield (i, next(it))
        i += 1

对于已经使用枚举的人来说,上述方法的性能较差,因为它必须检查是否该停止每个迭代了。如果没有得到stop参数,我们可以检查并使用旧的枚举:

_enumerate = enumerate

def enumerate(collection, start=0, stop=None):
    if stop is not None:
        return zip(range(start, stop), collection)
    return _enumerate(collection, start)

这种额外的检查对性能的影响可以忽略不计。

关于为什么枚举没有停止参数,这是最初提出的(请参阅PEP 279):

最初建议使用此函数以及可选的start和stop参数。GvR [Guido van Rossum]指出,该函数调用enumerate(seqn, 4, 6)具有一种替代的,合理的解释,即是一个片,该片将返回序列的第四和第五个元素。为了避免歧义,可选参数被删除,即使这意味着失去作为循环计数器的灵活性。对于从一个数开始计数的常见情况,这种灵活性最为重要,例如:

for linenum, line in enumerate(source,1):  print linenum, line

显然start是因为它非常有价值而保留了它,而stop因为它用例较少被放弃了,并导致了对新功能使用的混淆。

避免使用下标符号进行切片

另一个答案是:

为什么不简单使用

for item in items[:limit]: # or limit+1, depends

这里有一些缺点:

  • 它仅适用于接受切片的可迭代对象,因此它的功能更加有限。
  • 如果它们确实接受切片,则通常会在内存中创建一个新的数据结构,而不是遍历引用数据结构,从而浪费了内存(切片时所有内置对象都会创建副本,但是例如,切片时numpy数组会生成视图)。
  • 无法分割的可迭代项将需要另一种处理方式。如果您切换到惰性评估模型,则还必须通过切片来更改代码。

仅当您了解限制及其构成副本或视图时,才应使用带下标符号的切片。

结论

我想现在Python社区知道枚举的用法,参数的值会超过混淆的代价。

在此之前,您可以使用:

for index, element in zip(range(limit), items):
    ...

要么

for index, item in enumerate(islice(items, limit)):
    ...

或者,如果您根本不需要索引:

for element in islice(items, 0, limit):
    ...

除非您了解这些限制,否则请避免使用下标符号进行切片。

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章