ПрограммированиеBackend разработчик

Опишите механизм работы списковых выражений (list comprehensions) в Python. Чем list comprehension отличается от функции map() и for-циклов, в чем плюсы и минусы каждого подхода, и какие ошибки могут возникнуть при неумелом применении?

Проходите собеседования с ИИ помощником Hintsage

Ответ.

Списковые выражения (list comprehensions) — это лаконичный способ создания списков на основе существующих итерируемых объектов с помощью короткого синтаксиса:

squares = [x**2 for x in range(10)]

Эта запись эквивалентна:

squares = [] for x in range(10): squares.append(x**2)

У list comprehensions есть несколько преимуществ:

  • Краткость и читаемость (особенно для простых трансформаций);
  • Возможность включать условия (filter): evens = [x for x in range(10) if x % 2 == 0];
  • Выражения возвращают список сразу, их результат можно использовать далее.

Аналог с map():

def f(x): return x**2 squares = list(map(f, range(10)))

map быстрее на больших данных, если используется функция, уже существующая на C, и подходит для применения одной функции ко всем элементам. List comprehension — для любых выражений, а не только готовых функций. Фор-цикл гибче, но громоздок.


Вопрос с подвохом.

Почему в выражении вида [x for x in range(10)] переменная x после выполнения списка оказывается доступна вне выражения в Python2, но не в Python3?

Ответ: В Python2 переменная цикла (x) сохраняет своё значение после выполнения list comprehension. В Python3 — она "изолируется" и вне списка недоступна, что предотвращает нежелательные побочные эффекты.

Пример:

# Python 2.x: [x for x in range(3)] print(x) # x == 2 # Python 3.x: [x for x in range(3)] print(x) # NameError: name 'x' is not defined

Примеры реальных ошибок из-за незнания тонкостей темы.


История 1

Один разработчик на крупном проекте хотел фильтровать и создавать новый список через list comprehension:

my_list = [item.transform() for item in data if item.is_valid()]

Но операция item.transform() вызывала ошибку, если item.is_valid() возвращал False. Однако функция проверки была написана с потенциальным side effect, и в итоге list comprehension неочевидно ломал части кода с побочными эффектами.


История 2

В проекте при миграции с Python2 на Python3 разработчик был уверен, что переменная цикла останется доступной:

[x for x in range(5)] print(x) # Ожидал получить 4, но получил NameError.

Это вызвало баг в циклической логике, где переменная должна была оставаться востребованной вне comprehension.


История 3

Использование вложенных списковых выражений без явного указания уровней:

def flatten(matrix): return [cell for row in matrix for cell in row]

У новичков часто возникают ошибки из-за ошибочного порядка обхода (например, [cell for cell in row for row in matrix] или лишнего вложения), что приводит к неверному результату — одномерный список вместо двумерного или наоборот.