Как ограничить итерации цикла в 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 перечислил, что составляет примерно половину этого кода:

 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) ), который, я думаю, был бы идеальным, но ниже не существует (см. Документацию по перечислению здесь ):

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

Обратите внимание, что нет необходимости указывать index потому что нет необходимости ссылаться на него.

Есть ли идиоматический способ написать выше? Как?

Второй вопрос: почему это не встроено в перечисление?

3 Solutions collect form web for “Как ограничить итерации цикла в Python?”

Как ограничить итерации цикла в 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, zip и range возвращают итерации, которые конвейерные данные вместо материализации данных в списках для промежуточных шагов.

 for _, item in zip(range(limit), items): print(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 ?

Вот перечисление, реализованное в чистом 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) 

Эта дополнительная проверка будет иметь незначительное незначительное влияние на производительность.

Что касается того, почему перечисление не имеет аргумента stop, это было первоначально предложено (см. 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 видят, когда нарезанные ).
  • Unsliceable iterables потребует другого вида обработки. Если вы переключитесь на ленивую оценочную модель, вам также придется изменить код с нарезкой.

Вы должны использовать только разрезание с нотной записью, если вы понимаете ограничения и делает ли это копию или представление.

Вывод

Я бы предположил, что теперь сообщество Python знает об использовании перечисления, стоимость путаницы будет перевешиваться значением аргумента.

До этого времени вы можете использовать:

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

или, если вам не нужен индекс вообще:

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

И избегайте нарезки с нотной надписью, если вы не понимаете ограничений.

Почему бы просто не использовать

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

Это будет работать только для некоторых итераций, но поскольку вы указали Списки, это работает.

Это не работает, если вы используете Sets или dicts и т. Д.

Вы можете использовать itertools.islice для этого. Он принимает аргументы start , stop и step , если вы передаете только один аргумент, тогда он считается 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) --> AB islice('ABCDEFG', 2, 4) --> CD islice('ABCDEFG', 2, None) --> CDEFG islice('ABCDEFG', 0, None, 2) --> ACEG 
Python - лучший язык программирования в мире.