簡體   English   中英

如何限制 Python 中循環的迭代?

[英]How can I limit iterations of a loop in Python?

假設我有一個項目列表,我想迭代其中的前幾個:

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

幼稚的實現

來自其他語言的 Python naïf 可能會編寫這個非常有用和高性能(如果單語)的代碼:

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 有 enumerate,它很好地包含了大約一半的代碼:

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

所以我們將額外的代碼減半。 但一定有更好的方法。

我們可以近似下面的偽代碼行為嗎?

如果 enumerate 采用另一個可選的stop參數(例如,它采用這樣的start參數: enumerate(items, start=1) ),我認為這是理想的,但以下不存在(請參閱enumerate 上文檔在這里):

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

請注意,不需要命名index因為不需要引用它。

有沒有一種慣用的方式來寫上面的內容? 怎么樣?

第二個問題:為什么這不內置在枚舉中?

如何限制 Python 中循環的迭代?

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

有沒有更短的、慣用的方式來寫上面的內容? 怎么樣?

包括索引

zip在其參數中最短的可迭代對象上停止。 (與zip_longest的行為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 中獲得相同的行為,只需將xrange替換為range並將itertools.izip替換為zip

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 所指出的,我們也可以用 enumerate 組合 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

對於那些已經使用 enumerate 的人來說,上面的性能會降低,因為它必須檢查是否是時候停止每次迭代了。 如果沒有停止參數,我們可以檢查並使用舊的枚舉:

_enumerate = enumerate

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

這種額外的檢查對性能的影響可以忽略不計。

至於為什么enumerate 沒有停止參數,這是最初提出的(參見PEP 279 ):

這個函數最初是用可選的開始和停止參數提出的。 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 社區知道 enumerate 的用法,混淆成本將被參數的價值所抵消。

在此之前,您可以使用:

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

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

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

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

並避免使用下標符號進行切片,除非您了解這些限制。

您可以為此使用itertools.islice 它接受startstopstep參數,如果您只傳遞一個參數,則將其視為stop 它適用於任何可迭代對象。

itertools.islice(iterable, stop)
itertools.islice(iterable, start, stop[, step])

演示:

>>> from itertools import islice
>>> items = list(range(10))
>>> limit = 5
>>> for item in islice(items, limit):
    print item,
...
0 1 2 3 4

來自文檔的示例:

islice('ABCDEFG', 2) --> A B
islice('ABCDEFG', 2, 4) --> C D
islice('ABCDEFG', 2, None) --> C D E F G
islice('ABCDEFG', 0, None, 2) --> A C E G

為什么不簡單地使用

for item in items[:limit]: # or limit+1, depends
    print(item)    # or whatever function of that item.

這僅適用於某些可迭代對象,但由於您指定了列表,因此它有效。

如果您使用 Sets 或 dicts 等,則它不起作用。

使用枚舉內的限制通過 islice

a = [2,3,4,2,1,4]

for a, v in enumerate(islice(a, 3)): 
   print(a, v)

輸出:

0 2
1 3
2 4

為什么不循環直到限制或列表末尾,以較早發生者為准,如下所示:

items = range(10)
limit = 5
for i in range(min(limit, len(items))):
  print items[i]

輸出:

0
1
2
3
4

簡短的解決方案

items = range(10)
limit = 5

for i in items[:limit]: print(i)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM