Генератор списка в Python: цикл в одну строку
Цикл сбора списка — из трёх строк в одну: выражение, for, условие. Разбираем анатомию генератора списка, фильтр через if, превращение через if-else и границу, за которой однострочник вредит.
Редакция Питоники
Цикл for собирает новый список из старого — самая частая работа в Python: удвоить числа, отобрать дорогие позиции, привести слова к верхнему регистру. Python позволяет записать такой цикл одной строкой — генератором списка. Разбираем анатомию записи, фильтр через if, превращение через if-else и границу, за которой однострочник только вредит.
От цикла — к одной строке
Задача: из чисел получить их удвоения. Цикл for решает её в три строки:
numbers = [1, 2, 3, 4]
doubled = []
for n in numbers:
doubled.append(n * 2)
print(doubled)
[2, 4, 6, 8]
Шаблон один и тот же для любой задачи: пустой список, цикл, append с выражением. Меняется только выражение перед append — значит, всё остальное можно свернуть в одну строку.
numbers = [1, 2, 3, 4]
doubled = [n * 2 for n in numbers]
print(doubled)
[2, 4, 6, 8]
Тот же результат — одна строка. Читается справа налево: для каждого n из списка numbers — взять n * 2. Квадратные скобки говорят: собирается новый список. Сам старый список не меняется — comprehension всегда строит новый.
Выражение — любое
Вместо n * 2 в записи может стоять любое выражение: вызов функции, метод, арифметика.
words = ['кот', 'енот', 'пёс']
lengths = [len(w) for w in words]
upper = [w.upper() for w in words]
print(lengths)
print(upper)
[3, 4, 3] ['КОТ', 'ЕНОТ', 'ПЁС']
За один короткий проход собрали длины слов, за второй — верхний регистр. Выражение вычисляется заново для каждого элемента: len(w) для первого, для второго, для третьего.
Фильтр: if после for
Второй ингредиент генератора списка — условие. Оно делает то же, что continue в обычном цикле из урока о continue: отсеивает неподошедшие элементы.
temps = [18, 25, 30, 11, 27]
hot = [t for t in temps if t >= 25]
print(hot)
[25, 30, 27]
if в конце работает фильтром: элемент попадает в новый список, только если условие истинно. Из пяти температур прошли три жарких. Фильтрующий if ничего не превращает — он лишь пропускает или нет.
numbers = [1, 2, 3, 4]
# SyntaxError: у фильтрующего if нет else
broken = [n * 2 for n in numbers if n > 2 else 0]
Превращение: if-else перед for
Если из каждого элемента нужно выбрать одну из двух веток, if-else ставят перед for:
temps = [18, 25, 30, 11, 27]
marks = ['жарко' if t >= 25 else 'ок' for t in temps]
print(marks)
['ок', 'жарко', 'жарко', 'ок', 'жарко']
Это превращение, а не фильтр: обрабатывается каждый элемент, вопрос лишь в том, что именно он даст. Жарким достаётся слово жарко, остальным — ок. Запомните положение: фильтр — после for, превращение — до for.
Фильтр и превращение сочетаются в одной записи: сначала выражение, потом for, потом фильтр.
prices = [89, 42, 590, 60]
cheap = [f'{p} руб.' for p in prices if p < 100]
print(cheap)
['89 руб.', '42 руб.', '60 руб.']
Читается слева направо: выражение f'{p} руб.' — что взять; for p in prices — откуда; if p < 100 — кого брать. Три части, порядок фиксированный: выражение, for, фильтр.
Два условия в фильтре
В фильтре можно поставить не одну проверку, а несколько — просто напишите их подряд, каждая со своим if. Логика та же, что у and: элемент попадёт в список, только если выполнены все условия.
nums = range(1, 11)
evens = [n for n in nums if n % 2 == 0 if n > 4]
print(evens)
[6, 8, 10]
Два условия подряд читаются как «чётные и те, что больше четырёх». Результат — 6, 8 и 10: двойка и четыре отпали на второй проверке. То же самое записывается одним if через and: if n % 2 == 0 and n > 4 — так привычнее, если проверок две или три. А вот длинную цепочку лучше выносить в отдельную функцию: иначе строка перестанет помещаться на экран.
Когда одна строка вредит
Генератор списка создан, чтобы строить списки. Что будет, если вложить в него печать?
words = ['кот', 'пёс']
result = [print(w) for w in words]
print(result)
кот пёс [None, None]
Слова напечатались — и собрался лишний список [None, None]: comprehension собрал результаты print, а print возвращает None. Запись обманывает читателя: выглядит как сборка списка, работает как печать. Побочные эффекты — печать ради печати, запись в файл, изменение внешних переменных — работа обычного for.
Генератор словаря и генератор множества
Идея со скобками работает не только со списками. Фигурные скобки дают словарь, а фигурные скобки с двоеточием — множество. Правило одно: то, что стоит слева от двоеточия, становится ключом, то, что справа, — значением.
words = ['кот', 'пароход', 'ёж']
lengths = {w: len(w) for w in words}
print(lengths)
{'кот': 3, 'пароход': 7, 'ёж': 2}Словарь собрался из слов и их длин. Тот же приём часто встречается в разборе данных: dict(zip(keys, values)) делает то же самое, но из двух готовых списков, а генератор словаря берёт ключ и значение из одного прохода по данным.
Если двоеточия в фигурных скобках нет, получается множество — набор уникальных значений:
tags = ['a', 'b', 'a', 'c']
unique = {t for t in tags}
print(sorted(unique))
['a', 'b', 'c']
Повторяющийся тег 'a' остался в одном экземпляре — так работает множество. Функция sorted нужна затем, что порядок элементов множества не определён, а вывод в учебном примере должен быть предсказуемым. В реальном коде, когда порядок важен, множество вообще не подходит.
Вложенные генераторы списков
Внутри генератора можно поставить ещё один for. Тогда список строится в два прохода: сначала по внешнему набору, потом по внутреннему. Тот же приём, что и с вложенными циклами, только записанный одной строкой.
matrix = [[1, 2], [3, 4]]
flat = [x for row in matrix for x in row]
print(flat)
[1, 2, 3, 4]
Читается так: для каждой строки таблицы возьми все её элементы. Получился плоский список из четырёх чисел — ровно то, что нужно, когда данные пришли вложенными, а работать с ними удобнее одним списком. Внимание на порядок частей: внешний for идёт первым, внутренний — вторым, и путать их нельзя, иначе получится список строк, а не чисел.
Проверим, что однострочник даёт ровно то же, что и обычный цикл с append:
nums = [1, 2, 3, 4]
squares_loop = []
for n in nums:
squares_loop.append(n * n)
squares_comp = [n * n for n in nums]
print(squares_loop)
print(squares_comp)
print(squares_loop == squares_comp)
[1, 4, 9, 16] [1, 4, 9, 16] True
Списки совпали, и последняя строка это подтвердила: True. Значит, между однострочником и циклом с append нет никакой разницы в результате — есть разница в длине кода и в читаемости.
Итоговое правило корзины: одно выражение и один цикл — comprehension; два цикла, цепочка условий или действия — обычный for. А проверки «хотя бы один» и «все элементы» по списку лучше отдать встроенным any и all — они умеют строить свои ответы вместе с генераторами, урок пятнадцатый о них.
Однострочник, который перестал читаться
Граница проходит не по длине строки, а по смыслу. Пока в генераторе видно «что берём», «откуда» и «кого оставляем» — его читать легко. Как только появляются вложенные условия, длинные выражения и вызовы функций, строка перестаёт рассказывать историю и превращается в шифр.
nums = [1, 2, 3, 4, 5]
print([n * n for n in nums if n % 2 == 1])
[1, 9, 25]
Тут всё на месте: квадраты нечётных чисел. Одна строка, три части, всё читается. Сравните с обычным циклом — тот занял бы четыре строки и требовал отдельного списка. Но представьте, что к этому добавится второе условие, проверка на положительность и округление: здесь уже пора остановиться и написать обычный цикл, а правило вынести в отдельную функцию.
| for + append | Генератор списка | |
|---|---|---|
| Строк кода | три-четыре | одна |
| Читается | шаг за шагом | формулой справа налево |
| Фильтр | if и continue в теле | if после for |
| Побочные эффекты | уместны | дурной тон |
| Исходный список | не меняется | не меняется |
Сначала предскажи ответ в голове — это главный навык программиста.
nums = [1, 2, 3, 4, 5]
print([n for n in nums if n % 2 == 0])
words = ['да', 'нет', 'да']
print([len(w) for w in words])
nums = [3, 8, 5]
print(['б' if n > 5 else 'м' for n in nums])
1. Что соберёт запись [n * n for n in [1, 2, 3]]?
2. Где стоит фильтрующий if в генераторе списка?
3. Где стоит if-else, если нужно превращать каждый элемент?
4. Что comprehension делает с исходным списком?
5. Что вернёт запись [print(w) for w in words]?
6. Что соберёт {w: len(w) for w in ['кот', 'пароход']}?
7. Как получить плоский список из таблицы [[1, 2], [3, 4]]?
8. Можно ли в фильтре генератора написать два условия подряд?
Дан список оценок grades = [2, 5, 3, 4, 5]. Первой строкой соберите генератором список только пятёрок и напечатайте его. Второй строкой соберите слова: зачёт для оценок не ниже четвёрки, пересдача для остальных — и напечатайте список.
Что такое генератор списка в Python простыми словами?
Это цикл сбора списка, свёрнутый в одну строку в квадратных скобках: [n * 2 for n in numbers]. Читается справа налево — для каждого элемента взять выражение. Результат тот же, что у цикла с append, но короче.
Урок 1: первый цикл forЧем генератор списка отличается от обычного цикла for?
Почти только записью: comprehension собирает список одним выражением и не требует пустого списка и append. В остальном это тот же перебор — элементы читаются по одному, выражение считается для каждого.
Где правильно ставить if в генераторе списка?
Фильтрующий if — после for: он отбрасывает элементы и else ему не положен. if-else превращения — перед for, внутри выражения: он выбирает значение для каждого элемента. Перепутать места — значит получить SyntaxError или другой список.
Что такое генератор в круглых скобках?
Это generator expression — ленивая версия: пары значений вычисляются по запросу, а не сразу. Передаётся прямо в sum(), any(), max(): sum(n * 2 for n in numbers). Для списка используйте квадратные скобки — они материализуют результат.
Чем генератор списка отличается от генератора словаря?
Только скобками: квадратные дают список, фигурные с двоеточием — словарь, фигурные без двоеточия — множество. Слева от двоеточия пишется ключ, справа — значение.
Как получить плоский список из списка списков?
Вложенный генератор: [x for row in matrix for x in row]. Внешний for идёт по строкам, внутренний — по их элементам, и на выходе получается один общий список.
Урок 8: вложенные циклыПонравился урок? Сошлитесь на него
«Сам старый список не меняется — comprehension всегда строит новый.»
Скопируйте готовую ссылку в формате HTML, Markdown или чистый адрес и вставьте в статью на Habr, VC, Telegram-канал или свой блог — так о проекте узнают новые читатели.
Что дальше
Осталось в разделе: 20 из 20. Прогресс хранится в вашем браузере.
Что читать дальше
Циклы · Урок 1
Цикл for в Python: первый цикл
Первый цикл for: компьютер повторяет одну строку за вас. Перебираем список, разбираем переменную цикла и тело с отступами — и видим, зачем цикл вместо копипасты.
Циклы · Урок 7
Оператор continue в Python: пропуск итерации
continue пропускает остаток тела для текущего элемента и переходит к следующему: не выход из цикла, а фильтр в потоке. Сравниваем с break на одних данных.
Циклы · Урок 15
any и all в Python: проверка без ручного цикла
any и all отвечают на вопросы «есть ли хоть один» и «все ли до единого» одной строкой — вместо цикла с булевым флагом.
Похожие уроки по темам
Подобраны автоматически по пересечению тем и ключевых слов.
Словари · Урок 15
dict comprehension: словарь за строку
Dict comprehension собирает словарь за одну строку: квадраты чисел, цены со скидкой, фильтр по условию, инверсия ключей и значений — и словарь из списка через enumerate.
dict comprehension pythonгенератор словаря python
json · Урок 11
Фильтрация данных: найти нужное в JSON
Включение с условием для отбора, next с default для первого подходящего, any и all для вопросов ко всей коллекции — работаем с выгрузкой заказов.
python отфильтровать список словарейlist comprehension if python json
Циклы · Урок 5
Цикл while в Python: повторять, пока условие
while повторяет тело, пока условие верно: число шагов заранее неизвестно. Разбираем проверку условия, шаг к выходу и главный риск — бесконечный цикл.
python цикл whileцикл while python