List, dict и set comprehensions без нечитаемой вложенности

Comprehension строит новую коллекцию из итерируемого источника: выражение описывает результат, а следующие части задают перебор и фильтр. Такая запись хорошо подходит для одного ясного преобразования. Она перестаёт помогать, когда внутри скрыты побочные эффекты, несколько уровней условий или доменная логика, которой нужно имя.

Три формы результата

Квадратные скобки создают список, фигурные с одним выражением — множество, а пара key: value — словарь. Порядок элементов списка соответствует порядку успешного перебора. Множество устраняет дубликаты и не обещает смыслового порядка. В словаре повторный ключ получает последнее вычисленное значение.

rows = [
    {"name": "Ada", "score": 91, "active": True},
    {"name": "Lin", "score": 84, "active": True},
    {"name": "Ada", "score": 95, "active": False},
]

active_names = [row["name"].lower() for row in rows if row["active"]]
score_by_name = {row["name"]: row["score"] for row in rows}
initials = {row["name"][0] for row in rows}

print(active_names)
print(score_by_name)
print(initials == {"A", "L"})

Ожидаемый вывод — ['ada', 'lin'], затем {'Ada': 95, 'Lin': 84} и True. Значение 91 потерялось в словаре из-за второго ключа Ada; это часть контракта dict, а не случайный порядок.

Читайте слева направо как цикл

Запись [transform(item) for item in items if allowed(item)] соответствует циклу: взять элемент, проверить условие и добавить преобразованный результат. В нескольких for порядок остаётся таким же, как во вложенных циклах.

matrix = [[1, 2], [3, 4]]
flat = [value for row in matrix for value in row]
print(flat)

Результат — [1, 2, 3, 4]. Если порядок частей трудно восстановить без мысленного исполнения, обычные циклы с говорящими именами будут яснее.

Условие-фильтр и условное выражение

if после for исключает элементы: [x for x in values if x >= 0]. Условное выражение до for сохраняет каждый элемент, но выбирает его форму: [x if x >= 0 else 0 for x in values]. Переставлять эти конструкции нельзя: они отвечают на разные вопросы.

Вызов чистой функции внутри comprehension допустим и часто улучшает чтение. Если преобразование требует try/except, нескольких временных переменных, логирования или изменения внешнего состояния, вынесите функцию либо напишите цикл. Краткость измеряется не числом строк, а усилием читателя.

Как ограничить вложенность

Один простой фильтр и одно преобразование обычно читаются хорошо. Для сложного pipeline полезно дать имя промежуточному результату: сначала отфильтровать валидные строки, затем построить индекс. Это также позволяет отдельно проверить объём и содержимое каждого шага. Генераторное выражение в круглых скобках подходит, когда потребителю не нужна готовая коллекция, но его ленивое поведение следует учитывать отдельно.

Как обнаружить потерю данных в comprehension

Характерная тихая ошибка dict comprehension — потеря данных при повторяющихся ключах. Сравните len(rows) и len(score_by_name), постройте частоты ключей или сначала соберите значения по группам. Если дубликаты запрещены, проверьте их до создания словаря и сообщите конфликт. Другая ловушка — обратиться после comprehension к его переменной цикла: в Python 3 она имеет собственную область, поэтому возможен NameError. Используйте итоговую коллекцию, а не рассчитывайте на «последний» элемент скрытого цикла.

Строим три представления событий

Возьмите список событий со значениями user, kind и duration. Создайте список длительностей только успешных событий, множество пользователей и словарь последней длительности по пользователю. Затем найдите повторяющиеся имена и решите, действительно ли словарь «последнее значение» соответствует задаче. Самостоятельная проверка: перепишите самый сложный comprehension обычным циклом и попросите себя объяснить обе версии вслух; оставьте ту, где условия и порядок очевиднее.

Граница читаемого comprehension

Вопросы о фильтрах и формах comprehension

Всегда ли comprehension быстрее цикла?

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

Можно ли использовать else в фильтре после for?

Нет, фильтрующая часть содержит только условие включения. Если нужен выбор между двумя значениями, условное выражение с else ставится перед for.

Чем set comprehension отличается от передачи генератора в set?

Оба подхода могут получить одинаковое множество. Форма {expression for item in source} прямо сообщает тип результата, а set(expression for item in source) иногда удобна в цепочке вызовов.

Источники