В повседневной жизни разработчика часто встречается код, работающий с последовательностями. Это связано с тем, что итераторы встроены в Python и тесно интегрированы в стандартную библиотеку.
Итераторы и операции над ними обычно собираются в конвейеры для данных. Лишь в конце каждого конвейера стоит reduce()
или другой потребитель элементов, не передающий элементы дальше.
Большинство таких конвейеров состоит из двух видов операций:
- Преобразование отдельных элементов. Эту задачу выполняет функция
map()
. Она преобразует весь поток с помощью другой функции, обрабатывающей отдельные элементы - Изменение состава элементов, то есть фильтрация или размножение. Фильтровать данные умеет
filter()
. А ужеmap()
в паре сchain()
из модуляitertools
превращают каждый элемент в несколько, не меняя при этом уровень вложенности
Для примера представим, что мы хотим получить список чисел вида [0, 0, 2, 2, 4, 4...]
— то есть по две копии возрастающих четных чисел. Напишем подходящий конвейер:
# Получаем поток четных чисел
def is_even(x):
return x % 2 == 0
list(filter(is_even, range(20)))
# [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
# Удваиваем каждое
def dup(x):
return [x, x]
list(map(dup, filter(is_even, range(20))))
# [[0, 0], [2, 2], [4, 4], [6, 6], [8, 8], [10, 10], [12, 12], [14, 14], [16, 16], [18, 18]]
# Делаем конвейер опять плоским
from itertools import chain
list(chain(*map(dup, filter(is_even, range(20)))))
# [0, 0, 2, 2, 4, 4, 6, 6, 8, 8, 10, 10, 12, 12, 14, 14, 16, 16, 18, 18]
# Вариант в виде однострочника
list(chain(*map(lambda x: [x, x], filter(lambda x: x % 2 == 0, range(20)))))
# [0, 0, 2, 2, 4, 4, 6, 6, 8, 8, 10, 10, 12, 12, 14, 14, 16, 16, 18, 18]
Как видите, задача решается соединением готовых элементов, а не написанием всего кода вручную в виде цикла for
. Уже здесь виден минус нашего конструктора: если готовых функций над элементами или предикатов нет, то их либо приходится заранее объявлять, либо использовать lambda
.
Оба варианта неудобны. Когда другой человек читает наш код с отдельными функциями, ему приходится постоянно прыгать по коду туда-сюда. А lambda
просто смотрятся громоздко. Но отчаиваться не нужно: у Python есть синтаксис, который может упростить работу с конвейерами.
Генераторы списков
Попробуем решить ту же задачу другим способом:
[x for num in range(20) for x in [num, num] if num % 2 == 0]