Рассказываем с примерами кода о функциях модуля itertools – инструмента стандартной библиотеки Python, содержащего распространённые шаблоны итераторов. Бесконечные счётчики, сочетания и перестановки, итераторы среза и многое другое.
В декабре 2019 года мы подробно рассказали о модуле collections. Другой важный компонент стандартной библиотеки – itertiools.
Модуль itertools содержит строительные блоки итераторов, основанные на конструкциях из языков программирования APL, Haskell и SML. Ниже мы опишем набор быстрых и эффективных в отношении памяти инструментов, полезных как самостоятельно, так и в сочетании. Вместе они образуют «алгебру итераторов» для программ на чистом Python.
Цель публикации – в сжатой форме рассмотреть распространённые примеры и шаблоны использования модуля itertools.
Начнем с импорта:
Чтобы лучше запомнить функции модуля, мы не станем использовать конструкцию from ... import *, а будем обращаться к методам модуля через его имя.
Если вы владеете Jupyter Notebook, блокнот этой статьи доступен на GitHub.
1. Бесконечный счётчик
Функция itertools.count(start=0, step=1) создаёт бесконечный итератор. Можно задать начальное значение и шаг итерирования.
Пример использования итератора в zip-функции:
Чтобы продолжить счёт при прерывании выполнения программы передайте последнее значение новому объекту итератора в виде параметра start.
Если необходимо подсчитывать число вхождений элементов в список или кортеж, обратите внимание на Counter() из модуля collections.
2. Упаковка по более длинной последовательности
Если последовательности имеют неодинаковую длину, zip() ограничивается самой короткой:
Но такое сокращение может быть неудобно из-за потери информации. Чтобы сохранить обе последовательности, используйте itertools.zip_longest():
Вместо None функция может подставлять значение, переданное аргументу fillvalue.
3. Аккумулирующий итератор
Суммирование нарастающим (накопительным) итогом – вид сложения последовательности чисел. Например, так считается квартальная прибыль Каждый элемент складывается с суммой всех предшествовавших элементов. В следующем примере 1 и 2 даёт 3, сумма 1, 2 и 3 равна 6 и т. д. Описанный тип работы с последовательностью воплощен в itertools.accumulate(iterable, func=operator.add, *, initial=None):
Чтобы вывести данные, мы используем список. При печати самого итератора выводится только его ссылка.
По умолчанию к элементам применяется operator.add. Можно, например, указать оператор умножения:
4. Бесконечный итератор последовательности
С помощью itertools.cycle() создаётся кольцевой итератор. Прийдя к последнему значению, он вновь начинает с первого:
5. Бесконечный итератор одного объекта
Итератор, создаваемый itertools.repeat() это вырожденный случай itertools.cycle(). Вместо последовательности повторяется одно и то же значение. Бесконечно или times раз:
Классический пример использования itertools.repeat() – итератор для map():
Все числа последовательности nums возведены в степень 2. Итератор вызывается столько раз, сколько необходимо, не нужно думать о числе элементов в последовательности.
6. Мапирование с распаковкой
Раз мы заговорили о map(), полезно рассказать и о itertools.starmap(). Этот метод принимает функцию и список кортежей аргументов. Как если бы использовался оператор *, отсюда и название:
7. Комбинаторика: сочетания
Модуль itertools позволяет решать программные задачи, построенные на структурах комбинаторики.
Сочетания – выбранные из множества n объектов комбинации m объектов, отличающиеся хотя бы одним объектом. Порядок элементов не важен.
Например, мы хотим составить трёхцветный флаг из лент цветных тканей. Есть четыре цвета лент. Все варианты выбора тканей без учёта их расположения:
Порядок следования не имеет значения, поэтому все тройки цветов уникальны.
8. Комбинаторика: перестановки
Перестановки – те же сочетания, для которых важен порядок следования элементов. В продолжение предыдущего примера определим все варианты как мы можем составить флаг с учётом порядка следования цветов:
Как и ожидалось, число комбинаций с учетом порядка элементов гораздо больше.
9. Комбинаторика: размещение с повторениями
Размещение с повторениями (выборка с возвращением) – это комбинаторное размещение объектов, в котором каждый объект может участвовать в размещении несколько раз.
Например, есть пин-код из четырех цифр. На каждой позиции стоит цифра от 0 до 9. Позиции не зависят друг от друга. Переберем все возможные коды:
10. Комбинаторика: размещение
Рассмотрим также случай обычного размещения, когда элементы могут повторяться, но каждое сочетание встречается только один раз:
11. Декартово произведение множеств
Метод itertools.product() можно использовать не только для размещений с повторениями.
Декартово (прямое) произведение – множество, элементами которого являются все возможные упорядоченные пары элементов исходных множеств.
Например, найдём обозначения всех полей шахматной доски:
12. Цепочки итераторов
Иногда необходимо использовать нескольков итераторов. И независимо, и цепочкой один за другим. Для объединения итераторов используйте itertools.chain(*iterables).
Например, мы хотим использовать для отрисовки игральных карт независимые итераторы обозначений в углу поля карты:
С помощью itertools.chain() также можно добавлять отдельные элементы в начало итератора:
13. Плоский список из вложенного
Альтернативным конструктором itertools.chain() служит itertools.chain.from_iterable(). Метод принимает один итерируемый объект. Сравните их вызовы:
Последний конструктор удобно использовать для объединения списков:
14. Итератор среза
Срез – удобный инструмент списков, который доступен и для итераторов с помощью itertools.islice().
Например, нам достаточно читать из крупного файла только три первых строки:
Функция itertools.islice() позволяет итерироваться по любым объектам в формате среза. Например, следующая функция возвращает n первых элементов итерируемого объекта в виде списка:
15. Фильтрация группы элементов
Функция compress() оставляет из итерируемых данных только те, что соответствуют позициям булевых селекторов:
Метод itertools.filterfalse() дополняет обычный фильтр filter():
16. Фильтрация до последнего истинного (или с первого ложного) элемента
Если необходимо отобрать объекты, стоящие после неудовлетворяющего условию элемента, используем itertools.dropwhile():
Метод itertools.takewhile() наоборот выведет элементы, удовлетворяющие условию вплоть до объекта, прерывающего цепочку истинных элементов:
17. Группировка по ключу
Инструмент itertools.groupby() объединяет смежные словари в группы по общему ключу. Например, сгруппируем студентов с одинаковой оценкой:
Обратите внимание, что группировка работает только со смежными объектами. Предварительно отсортируйте данные.
18. Репликация итераторов
Функция itertools.tee() создаёт из одного итерируемого объекта два итератора:
Эти итераторы соответствуют одной последовательности, но независимы друг от друга.
19. Повторение последовательности заданное число раз
Волшебная сила itertools – в умении комбинировать итераторы, чтобы писать быстрый, эффективный и ясный код.
Например, сочетание itertools.chain() и itertools.from_iterable() даёт ограниченный вариант бесконечного itertools.cycle():
20. Уникальные элементы последовательности
Напоследок напишем функцию, выводящую все уникальные элементы в том же порядке, как они появлялись в исходной последовательности. Опциональный аргумент – ссылка на функцию определения эквивалентов, представленных по-разному. Например, заглавных и строчных букв.
Заключение
Описывая приёмы использования itertools, мы попутно определили основные функции модуля.
Итераторы полезны для обработки крупных файлов и потоков данных, для доступа к содержимому объектов без раскрытия их полного внутреннего представления.
Модуль itertools обеспечивает ключевые структуры итераторов Python. Другие шаблоны вы найдёте в специальной библиотеке примеров more-itertools:
Интересны ли вам такие обзоры инструментов Python? Будем рады узнать в комментариях.