Курс по стандартной библиотеке: https://stepik.org/a/259466?utm_source=proproprogs
Начиная с этого
занятия, мы с вами будем рассматривать возможности весьма популярного модуля itertools стандартной
библиотеки Python. Этот модуль
включает ряд широко используемых функций для обработки последовательностей с
использованием механизма итераторов. Напомню, что итератор – это универсальный
интерфейс для перебора элементов любых типов последовательностей. К тому же
итераторы не хранят все данные сразу в памяти, а генерируют их по мере
необходимости. Благодаря этому эффективно расходуется память устройства.
Объект-итератор
в языке Python для любых
итерируемых объектов можно получить с помощью функции iter. Например:
d = {'a': 1, 'b': 2}
lst = [1, 2, 3]
s = "hello"
it1 = iter(d) # итератор для перебора словаря
it2 = iter(lst) # итератор для перебора списка
it3 = iter(s) # итератор для перебора строки
А затем,
перебирать элементы, вызывая функцию next:
v = next(it3) # 'h'
v = next(it3) # 'e'
На основе таких
итераторов построены функции модуля itertools. Их можно разделить
на следующие группы:
- объединение и
разделение итераторов: chain(), zip_longest(), …
- комбинирование данных: product(),
permutations(), combinations(), …
- фильтрация
и выборка элементов: filterfalse(), dropwhile(), takewhile(), ...
- инфинитные (бесконечные)
генераторы: count(), cycle(), repeat(), ...
- дополнительные
функции: groupby(), starmap(), …
Рассмотрение
начнем с группы объединения и разделения итераторов.
Функция chain()
Первая функция chain() позволяет
объединять несколько итерируемых объектов в единую последовательность. Пусть,
например, имеются следующие объекты:
lst = [1, 2, 3]
s = {'a', 'b', 'c', 'd'}
d = {'key1': 1, 'key2': 2}
Все их можно
объединить и перебрать в виде единой последовательности следующим образом:
chained_it = itertools.chain(lst, d, s)
Получаем
итератор chained_it, с помощью
которого можно перебирать элементы. Сделать это можно несколькими способами.
Либо воспользоваться циклом for:
for x in chained_it:
print(x, end=" ")
Увидим вывод:
1 2 3 key1 key2
b d c a
Либо на основе chained_it сформировать
какую-либо новую коллекцию, например, список:
chained_lst = list(chained_it) # [1, 2, 3, 'key1', 'key2', 'a', 'd', 'c', 'b']
Обратите
внимание, что объединенные данные возвращаются строго в порядке их записи в
функции chain(): список,
словарь, множество. И это всегда так. Если порядок изменить, то изменится и
порядок следования элементов:
chained_it = itertools.chain(d, s, lst)
chained_lst = list(chained_it) # ['key1', 'key2', 'b', 'c', 'd', 'a', 1, 2, 3]
Таким образом,
мы получаем возможность эффективно (с точки зрения удобства и расхода памяти)
обрабатывать смешанные структуры данных единым способом. Соответственно,
функция chain() применяется в
задачах, где требуется условное объединение данных (на уровне итераторов) с их
последующей обработкой. Классический пример – это чтение данных из нескольких
файлов с последующим объединением результата:
import itertools
with open('words1.txt', encoding='utf-8') as f1, open('words2.txt', encoding='utf-8') as f2:
lines = itertools.chain(f1.readlines(), f2.readlines())
for line in lines:
print(line.strip())
Здесь читаются
строки из двух файлов 'words1.txt' и 'words2.txt', а затем,
формируется общий итератор lines для
последовательного перебора обоих списков циклом for.
Объединение вложенных данных
До сих пор мы
использовали одномерные последовательности для их объединения. А что будет,
если в функцию chain() передать вложенные списки? Давайте
посмотрим. Пусть у нас задан генератор вложенных списков и обычный двумерный
список с названиями городов и стран:
data_gen = ([x, x ** 2] for x in range(1, 7))
data_geo = [["Уфа", "Пермь", "Дубай"], ["Россия", "ОАЭ", "Китай"]]
Тогда их
объединение:
chained_it = itertools.chain(data_gen, data_geo)
chained_lst = list(chained_it)
print(chained_lst)
даст следующий
результат:
[[1, 1], [2, 4],
[3, 9], [4, 16], [5, 25], [6, 36], ['Уфа', 'Пермь', 'Дубай'], ['Россия', 'ОАЭ',
'Китай']]
Вполне ожидаемо.
Мы видим соответствующие элементы заданных одномерных последовательностей. То
есть, вложения, даже если это итерируемые объекты, не перебираются. Однако
здесь для их перебора можно воспользоваться функцией chain.from_iterable(),
которая дополнительно объединит вложенные итерируемые объекты:
chained_it = itertools.chain.from_iterable(data_gen)
Получим
результат:
[1, 1, 2, 4, 3,
9, 4, 16, 5, 25, 6, 36]
Функция chain.from_iterable()
принимает только один итератор или итерируемый объект, объединяя вложенные
итерируемые объекты. Но, если уровней вложенности будет более двух, например:
data = [list(data_gen), data_geo]
chained_it = itertools.chain.from_iterable(data)
то рекурсивного
перебора вглубь вложений выполняться не будет:
[[1, 1], [2, 4],
[3, 9], [4, 16], [5, 25], [6, 36], ['Уфа', 'Пермь', 'Дубай'], ['Россия', 'ОАЭ',
'Китай']]
Таким образом, функция
chain() помогает гибко и эффективно объединять различные последовательности
данных, позволяя избегать затрат ресурсов на создание огромных временных
структур данных. Она отлично подходит для ситуаций, связанных с обработкой
разнотипных коллекций, особенно при ограничениях памяти и производительности.
Курс по стандартной библиотеке: https://stepik.org/a/259466?utm_source=proproprogs