Генератор списка Python: фильтрация и преобразование без запутанного цикла

Генератор списка Python: фильтрация и преобразование без запутанного цикла
Содержание статьи

Генератор списка в Python, или list comprehension, создаёт новый список по правилу [выражение for элемент in источник if условие]. Выражение задаёт результат, цикл перебирает источник, а необязательное условие отбирает элементы. Такой приём удобен для одного понятного преобразования, но не обязан заменять любой цикл.

В примере Mentoring.digital возьмём учебные заказы: получим номера оплаченных заказов и подпись для каждого. Данные уже имеют известную структуру; проверку внешнего JSON нужно выполнять до этого шага.

Фильтр и if else решают разные задачи

orders = [
    {"id": 1, "paid": True, "total": 1200},
    {"id": 2, "paid": False, "total": 800},
    {"id": 3, "paid": True, "total": 500},
]
paid_ids = [order["id"] for order in orders if order["paid"]]
labels = ["paid" if order["paid"] else "waiting" for order in orders]
print(paid_ids)  # [1, 3]
print(labels)   # ['paid', 'waiting', 'paid']

В paid_ids условие стоит после for и исключает неоплаченный заказ из результата. Поэтому элементов два. В labels условное выражение находится перед for: оно выбирает одну из двух строк для каждого заказа. Поэтому элементов три.

Ошибка [x for x in items if condition else other] возникает из-за смешения этих конструкций. Если нужно изменить значение каждого элемента, поставьте if ... else ... в выражение слева. Если нужно убрать неподходящие элементы, оставьте фильтр справа. Эти возможности можно сочетать, но сначала проверьте, легко ли прочитать правило вслух.

Разверните выражение в обычный цикл

paid_ids = []
for order in orders:
    if order["paid"]:
        paid_ids.append(order["id"])

Это тот же отбор с тем же порядком результата. List comprehension не сортирует и не удаляет повторы сам по себе. Два оплаченных заказа с одинаковым id дадут два одинаковых значения. Если источник пустой, получится пустой список.

Не используйте [print(x) for x in items] только ради вывода. Такое выражение создаёт ненужный список результатов вызова. Обычный цикл лучше выражает действие с побочным эффектом. Он также удобнее, когда внутри нужны обработка исключений, несколько промежуточных переменных или отдельное сообщение об ошибке.

Чем список отличается от выражения-генератора

squares = [n * n for n in range(4)]
lazy_squares = (n * n for n in range(4))
print(list(lazy_squares))  # [0, 1, 4, 9]
print(list(lazy_squares))  # []

Квадратные скобки строят список сразу. Круглые скобки в этом примере создают выражение-генератор: значения вычисляются по мере запроса, а после полного обхода этот объект исчерпан. Для повторного обхода нужен новый генератор или сохранённый список.

Если требуется только сумма, можно передать генератор непосредственно в sum(n * n for n in range(4)). Это позволяет не создавать отдельный список квадратов. Из этого не следует универсальное обещание ускорения: выбор зависит от размера данных, повторных обходов и работы внутри выражения.

Как читать вложенные циклы и проверить решение

rows = [[1, 2], [], [3]]
flat = [value for row in rows for value in row]
assert flat == [1, 2, 3]

Порядок for здесь такой же, как в обычных вложенных циклах: сначала строка, затем её элементы. Сравните с парами [(x, y) for x in [1, 2] for y in [3, 4]], где получается четыре комбинации, а не попарное соединение двух списков.

Для самостоятельной проверки добавьте пустой источник, один подходящий элемент, повторы и элемент, не проходящий фильтр. Если правило стало трудно объяснить без расшифровки, вынесите преобразование в именованную функцию или верните обычный цикл.

На курсе Python с наставником такие задачи помогают связать коллекции с обработкой реальных данных. Для разбора уже написанного кода покажите ментору по Python исходный пример, ожидаемый список и случай, в котором результат отличается.

Похожие статьи по данной теме