Перейти к содержанию

412 уроков, 25 библиотек и челлендж «Что выведет код?» — бесплатно, код прямо в браузере

Начать обучение
Урок 12 из 20 Начальный 15 мин 100 XP

Генератор списка в Python: цикл в одну строку

Цикл сбора списка — из трёх строк в одну: выражение, for, условие. Разбираем анатомию генератора списка, фильтр через if, превращение через if-else и границу, за которой однострочник вредит.

Редакция Питоники

Цикл for собирает новый список из старого — самая частая работа в Python: удвоить числа, отобрать дорогие позиции, привести слова к верхнему регистру. Python позволяет записать такой цикл одной строкой — генератором списка. Разбираем анатомию записи, фильтр через if, превращение через if-else и границу, за которой однострочник только вредит.

От цикла — к одной строке

Задача: из чисел получить их удвоения. Цикл for решает её в три строки:

цикл с append
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 ничего не превращает — он лишь пропускает или нет.

так нельзя: else у фильтра
numbers = [1, 2, 3, 4]

# SyntaxError: у фильтрующего if нет else
broken = [n * 2 for n in numbers if n > 2 else 0]
Блок не запустится: SyntaxError. Фильтрующий if после for не принимает else — либо только фильтр, либо if-else превращения перед for.

Превращение: 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 — так привычнее, если проверок две или три. А вот длинную цепочку лучше выносить в отдельную функцию: иначе строка перестанет помещаться на экран.

Когда одна строка вредит

Генератор списка создан, чтобы строить списки. Что будет, если вложить в него печать?

печать внутри comprehension
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])
Проверь себя
0 / 8

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. Можно ли в фильтре генератора написать два условия подряд?

Карточки терминов
Запомнено: 0 / 9
Практика

Дан список оценок grades = [2, 5, 3, 4, 5]. Первой строкой соберите генератором список только пятёрок и напечатайте его. Второй строкой соберите слова: зачёт для оценок не ниже четвёрки, пересдача для остальных — и напечатайте список.

practice.py
Вопросы и ответы по уроку

Что такое генератор списка в 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-канал или свой блог — так о проекте узнают новые читатели.

TelegramVK

Что дальше

Осталось в разделе: 20 из 20. Прогресс хранится в вашем браузере.

Похожие уроки по темам

Подобраны автоматически по пересечению тем и ключевых слов.