Мастерство стандартной библиотеки python: бесконечные итераторы itertools
Продолжаем наше исследование модуля itertools
.
На очереди 3 конструктора бесконечных итераторов:
itertools.count
itertools.count
- это как range
, только он бесконечный и ленивый.
Кстати, если вы раньше не слышали термин ленивость (хотя я почему-то уверен, что мы все его слышали и даже практиковали) - то бегом читать хотя бы сюда. Когда-нибудь мы пройдем путями Дэвида Бизли и его легендарного 147 страничного манускрипта “Generator Tricks For Systems Programmers”, но не сегодня. Сегодня - об относительно простых вещах.
Вообще, count - это супер просто, он всего лишь считает до бесконечности. Ну или минус бесконечности, если step отрицательный.
И все.
Но есть нюанс. У него нет конца, то есть его нельзя сконсюмить.
Сконсюмить - это разом прочитать весь iterable, например для складирования в list.
Ну то есть можно, но вот эта питонья строчка гарантированно положит любую рабочую станцию. И да, быстро-быстро понажимать Ctrl+C не помогает) Только hard reset, я вас предупредил.
Как же с ним тогда работать, если его нельзя привести к list/set, на него нельзя “скастовать” sum и т.д.
Ну, во-первых по нему можно бежать (и вовремя из него выйти):
И его можно комбинировать с другими ленивыми итераторами, например map, zip, islice, accumulate и т.д.
Известно, что итераторы вроде map и zip при пробежке по нескольким iterable одновременно, заканчиваются когда хотя бы один из iterable заканчивается. Это дает нам exit из бесконечного итератора.
Пример взят из доков itertools.repeat
:
Мы не положили сервак, хотя вроде бы пытаемся сконсюмить через list
бесконечный repeat
. К счастью (или к печали), range
конечен и map
закончится вместе с ним.
Бесконечный итератор отказывается от своей бесконечности, чтобы закончиться вместе с конечной коллекцией. Прям какой-то вайб из старого фильма Горец и песни Queen - Who Wants To Live Forever
itertools.repeat
itertools.repeat
еще проще, чем itertools.count
. Он даже не считает, а просто повторяет одно и тоже либо указанное количество раз, либо бесконечно.
В доках itertools
приводится такое:
Мы приведем вот такой эквивалент для фиксированного количество повторений:
Легко также заметить, что itertools.count
при step=0 - это itertools.repeat
Наверное, repeat и count добавляют немного читаемости в код, и есть подозрение что они могут быть быстрее, но проверить итераторы на скорость не так просто, ведь они одноразовые, а тест скорости - это многократное повторение и сравнение.
Но давайте попробуем так:
itertools.repeat
оказался на порядок быстрее (один порядок - это в 10 раз в десятичной системе, если кто не знал)
К слову, покритикуйте мой тест на performance: как думаете, хороша ли “фабрика” с помощью lambda
и валидные ли получились цифры и выводы?
itertools.cycle
Бесконечный повтор. Супер просто:
Несмотря на простоту, эта штука очень удобна.
Я очень люблю ротировать прокси/юзерагенты с помощью itertools.cycle
, когда нужно что-то парсить/обходить на регулярной основе.
Например, можно объявить “глобальные” итераторы:
И каждый раз когда вам нужно сделать новый запрос, вы просто просите у “глобальных” итераторов новые значения через next
:
Получается такое распределенное итерирование из разных мест программы одновременно. Но при этом итератор - один на всех. Итератор как сервис, хех.
Как будто мы написали класс ProxyManager
и объявили у него ProxyManager.get
, который определяет выдачу нам новых проксей. Только вместо class
у нас itertools.cycle
, а вместо get
- у нас next
. Так нужно ли объявлять класс? :)
That’s all, folks!
Надеюсь, вам понравилось.
А есть что почитать дополнительно?
Конечно.