Шаг 69.
Python: сборник рецептов.
Итераторы и генераторы. Пропуск первой части итерируемого объекта

    На этом шаге мы рассмотрим решение этой задачи.

Задача

    Вы хотите итерировать по элементам в последовательности, но первые несколько элементов вам неинтересны, и вы хотите их опустить.

Решение

    В модуле itertools есть несколько функций, которые могут быть использованы для решения этой задачи. Первая - itertools.dmpwhile(). Чтобы использовать ее, вы предоставляете функцию и итерируемый объект. Возвращаемый итератор отбрасывает первые элементы в последовательности до тех пор, пока предоставленная функция возвращает True. А затем выдается вся оставшаяся последовательность.

    Предположим, что вы читаете файл, который начинается со строчек с комментариями:

>>> with open('/etc/passwd') as f:
	for line in f:
		print(line, end='')


##
#	User Database
#
#	Note that this file is consulted directly only when the system is running
#	in single-user mode. At other times, this information is provided by
#	Open Directory.
.   .   .   .
##
nobody:*:-2:-2:Unprivileged User:/var/empty:/usr/bin/false 
root:*:0:0:System Administrator:/var/root:/bin/sh
>>>

    Если вы хотите пропустить все начальные закомментированные строчки, вот как это можно сделать:

>>> from itertools import dropwhile
>>> with open('/etc/passwd') as f:
	for line in dropwhile(lambda line: line.startswith('#'), f):
		print(line, end='')


    nobody:*:-2:-2:Unprivileged User:/var/empty:/usr/bin/false root:*:0:0:System Administrator:/var/root:/bin/sh >>>

Этот пример показывает, как можно пропустить первые элементы в соответствии с возвращаемым значением проверочной функции. Если так случилось, что вы знаете точное количество элементов, которые хотите пропустить, то вы можете вместо вышеописанного способа использовать itertools.islice(). Например:
>>> from itertools import islice
>>> items = ['a', 'b', 'c', 1, 4, 10, 15]
>>> for x in islice(items, 3, None):
	print(x)

	
1
4
10
15
>>> 

    В этом примере последний аргумент islice() None необходим для того, чтобы обозначить, что вам нужно все за пределами первых трех элементов (а не первые три элемента). То есть срез [3:], а не [:3].

Обсуждение

    Главное преимущество функций dropwhile() и islice() в том, что они позволяют избежать написания грязного кода наподобие вот такого:

with open('/etc/passwd') as f:
    #  Пропускаем начальные комментарии 
    while True:
        line = next(f, '') 
        if not line.startswith('#'): 
            break

    #  Обрабатываем оставшиеся строки 
    while line:
        #  Можно заменить полезной обработкой 
        print(line, end='') 
        line = next(f, None)

    Отбрасывание первой части итерируемого объекта также немного отличается от простого фильтрования. Например, первая часть этого рецепта может быть переписана вот так:

with open('/etc/passwd') as f:
    lines = (line for line in f if not line.startswith('#')) 
    for line in lines:
        print(line, end='')

    Очевидно, что это отбросит все закомментированные строчки в начале файла, но такое решение отбросит и все остальные такие строчки во всем файле. С другой стороны, решение, которое отбрасывает все элементы до тех пор, пока не будет встречен элемент, не соответствующий условиям отбрасывания, подходит под наши требования: все последующие элементы будут возвращены без фильтрования.

    Стоит отметить, что этот рецепт работает со всеми итерируемыми объектами, включая те, размер которых нельзя оценить предварительно: генераторами, файлами и другими подобными объектами.

    На следующем шаге мы рассмотрим итерирование по всем возможным комбинациям и перестановкам.




Предыдущий шаг Содержание Следующий шаг