HTML Diff
0 added 0 removed
Original 2026-01-01
Modified 2026-02-26
1 <p>В повседневной жизни разработчика часто встречается код, работающий с последовательностями. Это связано с тем, что итераторы встроены в Python и тесно интегрированы в стандартную библиотеку.</p>
1 <p>В повседневной жизни разработчика часто встречается код, работающий с последовательностями. Это связано с тем, что итераторы встроены в Python и тесно интегрированы в стандартную библиотеку.</p>
2 <p>Итераторы и операции над ними обычно собираются в конвейеры для данных. Лишь в конце каждого конвейера стоит reduce() или другой потребитель элементов, не передающий элементы дальше.</p>
2 <p>Итераторы и операции над ними обычно собираются в конвейеры для данных. Лишь в конце каждого конвейера стоит reduce() или другой потребитель элементов, не передающий элементы дальше.</p>
3 <p>Большинство таких конвейеров состоит из двух видов операций:</p>
3 <p>Большинство таких конвейеров состоит из двух видов операций:</p>
4 <ol><li><strong>Преобразование отдельных элементов</strong>. Эту задачу выполняет функция map(). Она преобразует весь поток с помощью другой функции, обрабатывающей отдельные элементы</li>
4 <ol><li><strong>Преобразование отдельных элементов</strong>. Эту задачу выполняет функция map(). Она преобразует весь поток с помощью другой функции, обрабатывающей отдельные элементы</li>
5 <li><strong>Изменение состава элементов</strong>, то есть фильтрация или размножение. Фильтровать данные умеет filter(). А уже map() в паре с chain() из модуля itertools превращают каждый элемент в несколько, не меняя при этом уровень вложенности</li>
5 <li><strong>Изменение состава элементов</strong>, то есть фильтрация или размножение. Фильтровать данные умеет filter(). А уже map() в паре с chain() из модуля itertools превращают каждый элемент в несколько, не меняя при этом уровень вложенности</li>
6 </ol><p>Для примера представим, что мы хотим получить список чисел вида [0, 0, 2, 2, 4, 4...] - то есть по две копии возрастающих четных чисел. Напишем подходящий конвейер:</p>
6 </ol><p>Для примера представим, что мы хотим получить список чисел вида [0, 0, 2, 2, 4, 4...] - то есть по две копии возрастающих четных чисел. Напишем подходящий конвейер:</p>
7 <p>Как видите, задача решается соединением готовых элементов, а не написанием всего кода вручную в виде цикла for. Уже здесь виден минус нашего конструктора: если готовых функций над элементами или предикатов нет, то их либо приходится заранее объявлять, либо использовать lambda.</p>
7 <p>Как видите, задача решается соединением готовых элементов, а не написанием всего кода вручную в виде цикла for. Уже здесь виден минус нашего конструктора: если готовых функций над элементами или предикатов нет, то их либо приходится заранее объявлять, либо использовать lambda.</p>
8 <p>Оба варианта неудобны. Когда другой человек читает наш код с отдельными функциями, ему приходится постоянно прыгать по коду туда-сюда. А lambda просто смотрятся громоздко. Но отчаиваться не нужно: у Python есть синтаксис, который может упростить работу с конвейерами.</p>
8 <p>Оба варианта неудобны. Когда другой человек читает наш код с отдельными функциями, ему приходится постоянно прыгать по коду туда-сюда. А lambda просто смотрятся громоздко. Но отчаиваться не нужно: у Python есть синтаксис, который может упростить работу с конвейерами.</p>
9 <h2>Генераторы списков</h2>
9 <h2>Генераторы списков</h2>
10 <p>Попробуем решить ту же задачу другим способом:</p>
10 <p>Попробуем решить ту же задачу другим способом:</p>
11 <p>Это тоже однострочник. Выглядит он не очень удобно, но к такому синтаксису можно привыкнуть. Попробуем отформатировать все выражение:</p>
11 <p>Это тоже однострочник. Выглядит он не очень удобно, но к такому синтаксису можно привыкнуть. Попробуем отформатировать все выражение:</p>
12 <p>Теперь код стал похож на два вложенных цикла. Похожий код можно написать и на обычных циклах:</p>
12 <p>Теперь код стал похож на два вложенных цикла. Похожий код можно написать и на обычных циклах:</p>
13 <p>Код выглядит очень похоже, но есть два различия:</p>
13 <p>Код выглядит очень похоже, но есть два различия:</p>
14 <ul><li>В первом варианте мы создаем новый список, а во втором - изменяем заранее созданный</li>
14 <ul><li>В первом варианте мы создаем новый список, а во втором - изменяем заранее созданный</li>
15 <li>Первый вариант - это выражение, а второй - набор инструкций. Следовательно, первый вариант можно использовать как часть любых других выражений. При этом нам не пришлось объявлять вспомогательные функции, лямбды тоже не понадобились</li>
15 <li>Первый вариант - это выражение, а второй - набор инструкций. Следовательно, первый вариант можно использовать как часть любых других выражений. При этом нам не пришлось объявлять вспомогательные функции, лямбды тоже не понадобились</li>
16 </ul><p>Выражения вида [… for … in …] называются<strong>генераторами списков</strong>. Рассмотрим составляющие нового синтаксиса.</p>
16 </ul><p>Выражения вида [… for … in …] называются<strong>генераторами списков</strong>. Рассмотрим составляющие нового синтаксиса.</p>
17 <p>Генератор списков описывается так:</p>
17 <p>Генератор списков описывается так:</p>
18 <p>Рассмотрим этот шаблон подробнее:</p>
18 <p>Рассмотрим этот шаблон подробнее:</p>
19 <ul><li>ВЫРАЖЕНИЕ может использовать ПЕРЕМЕННУЮ и вычисляется в элемент будущего списка</li>
19 <ul><li>ВЫРАЖЕНИЕ может использовать ПЕРЕМЕННУЮ и вычисляется в элемент будущего списка</li>
20 <li>ПЕРЕМЕННАЯ - имя, с которым поочередно связываются элементы ИСТОЧНИКА</li>
20 <li>ПЕРЕМЕННАЯ - имя, с которым поочередно связываются элементы ИСТОЧНИКА</li>
21 <li>ИСТОЧНИК - любой итератор или итерируемый объект</li>
21 <li>ИСТОЧНИК - любой итератор или итерируемый объект</li>
22 <li>УСЛОВИЕ - выражение, которое использует ПЕРЕМЕННУЮ, вычисляемую на каждой итерации</li>
22 <li>УСЛОВИЕ - выражение, которое использует ПЕРЕМЕННУЮ, вычисляемую на каждой итерации</li>
23 </ul><p>Если условие оказывается ложным, то вычисление выражения для текущей итерации пропускается - в итоговый список новый элемент не добавится. Если условие вместе с ключевым словом if будет пропущено, то это будет эквивалентно условию if True.</p>
23 </ul><p>Если условие оказывается ложным, то вычисление выражения для текущей итерации пропускается - в итоговый список новый элемент не добавится. Если условие вместе с ключевым словом if будет пропущено, то это будет эквивалентно условию if True.</p>
24 <p>В общем случае переменных может быть несколько. Здесь тоже работает распаковка кортежей и списков, в том числе и вложенных.</p>
24 <p>В общем случае переменных может быть несколько. Здесь тоже работает распаковка кортежей и списков, в том числе и вложенных.</p>
25 <p>Вот несколько примеров:</p>
25 <p>Вот несколько примеров:</p>
26 <h2>Когда использовать генераторы списков</h2>
26 <h2>Когда использовать генераторы списков</h2>
27 <p>Выше мы увидели, что генераторы списков не отменяют все встроенные функции для работы с итераторами. Одно с другим отлично сочетается.</p>
27 <p>Выше мы увидели, что генераторы списков не отменяют все встроенные функции для работы с итераторами. Одно с другим отлично сочетается.</p>
28 <p>С другой стороны, лучше не смешивать генераторы списков с функциями map() и filter() - это как раз взаимозаменяемые сущности. Еще не стоит смешивать генераторы списков с какими-либо побочными эффектами. Дело в том, что генераторы позволяют писать довольно лаконичный и компактный код. Не нужно заставлять программиста думать, где и что поменяется при создании списка.</p>
28 <p>С другой стороны, лучше не смешивать генераторы списков с функциями map() и filter() - это как раз взаимозаменяемые сущности. Еще не стоит смешивать генераторы списков с какими-либо побочными эффектами. Дело в том, что генераторы позволяют писать довольно лаконичный и компактный код. Не нужно заставлять программиста думать, где и что поменяется при создании списка.</p>
29 <p>Это касается не только кода с функциями map() и filter(), но и вообще любых декларативных конвейеров. Стоит разделять код, написанный в разных парадигмах, на отдельные однотонные участки. Например, ввод-вывод - это один из основных видов побочных эффектов. Он может находиться в начале конвейера или в его конце, но не в середине.</p>
29 <p>Это касается не только кода с функциями map() и filter(), но и вообще любых декларативных конвейеров. Стоит разделять код, написанный в разных парадигмах, на отдельные однотонные участки. Например, ввод-вывод - это один из основных видов побочных эффектов. Он может находиться в начале конвейера или в его конце, но не в середине.</p>
30 <h2>Как проявляется декларативность генераторов списков</h2>
30 <h2>Как проявляется декларативность генераторов списков</h2>
31 <p>Разберем, чем генератор списка отличается от явно императивного двойного цикла. В цикле можно не только строить список, но и производить другие побочные эффекты - например, изменять объекты списка.</p>
31 <p>Разберем, чем генератор списка отличается от явно императивного двойного цикла. В цикле можно не только строить список, но и производить другие побочные эффекты - например, изменять объекты списка.</p>
32 <p>Побочные эффекты в циклах не считаются плохим тоном, ведь циклы и предназначены для выполнения повторяющихся действий. В свою очередь генераторы списков описывают, что из себя представляет каждый элемент, а не как его получить из внешнего мира или вывести в консоль.</p>
32 <p>Побочные эффекты в циклах не считаются плохим тоном, ведь циклы и предназначены для выполнения повторяющихся действий. В свою очередь генераторы списков описывают, что из себя представляет каждый элемент, а не как его получить из внешнего мира или вывести в консоль.</p>
33 <p>Можно взглянуть на отличающиеся части и увидеть, что:</p>
33 <p>Можно взглянуть на отличающиеся части и увидеть, что:</p>
34 <ul><li>Генератор списка описывает результат. Он говорит: "Результирующий список - это список чисел в диапазоне от 1 до 20</li>
34 <ul><li>Генератор списка описывает результат. Он говорит: "Результирующий список - это список чисел в диапазоне от 1 до 20</li>
35 <li>Процедурное решение показывает, как получить результат. Оно говорит: "Для каждого числа в диапазоне до 20 добавляем в список число"</li>
35 <li>Процедурное решение показывает, как получить результат. Оно говорит: "Для каждого числа в диапазоне до 20 добавляем в список число"</li>
36 </ul><p>Сами циклы for в обоих случаях выглядят одинаково, потому что в Python циклы более декларативные, чем в некоторых других языках. Таким образом, цикл for в Python считается императивным из-за его тела, а не из-за заголовка.</p>
36 </ul><p>Сами циклы for в обоих случаях выглядят одинаково, потому что в Python циклы более декларативные, чем в некоторых других языках. Таким образом, цикл for в Python считается императивным из-за его тела, а не из-за заголовка.</p>