ПрограммированиеData Engineer

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

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

Ответ.

История вопроса

Функция map() пришла из функционального программирования (еще из Lisp), её назначение — применять функцию к каждому элементу последовательности с возвратом нового итерируемого объекта с результатами. В Python она возникла задолго до появления генераторных и списковых выражений, но до сих пор широко используется, особенно в больших пайплайнах обработки данных.

Проблема

Если требуется изменить каждый элемент коллекции по какому-то правилу, решения вида for-циклов быстро становятся громоздкими и менее читаемыми. Списковые выражения и map() позволяют выразить намерение компактно. При этом важно понимать различия этих подходов, чтобы правильно выбрать инструмент.

Решение

  • map(func, iterable) создаёт ленивый итератор, в котором элементы исходной последовательности преобразованы функцией func
  • В Python 3 объект map не вычисляет элементы сразу, а работает лениво
def square(x): return x * x squares = map(square, [1, 2, 3]) print(list(squares)) # [1, 4, 9]

Списковое выражение [square(x) for x in [1, 2, 3]] делает то же, но сразу возвращает список, а генераторное выражение (square(x) for x in [1, 2, 3]) — это ленивый генератор.

Ключевые особенности:

  • map() всегда возвращает ленивый итератор
  • Удобна для работы с несколькими коллекциями (с несколькими аргументами) — map(f, a, b)
  • Не поддерживает условия фильтрации прямым синтаксисом (в отличие от list comprehension)

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

Возвращает ли map() список в Python 3?

Нет, начиная с Python 3, map() возвращает не список, а ленивый итератор. Чтобы получить список — нужно вручную обернуть результат в list().

res = map(str.upper, ['a', 'b']) print(res) # <map object ...> print(list(res)) # ['A', 'B']

Можно ли с помощью map() добавить условие фильтрации внутри функции?

Нет, map() не фильтрует элементы, а только преобразует. Для фильтрации используйте filter() или списковое выражение:

result = map(str.upper, ['a', 'b', None]) # Если прилетит None, map вызовет ошибку вызова str.upper(None) # filter поможет убрать None до map

Можно ли с помощью map() одновременно перебрать две последовательности?

Да, map() умеет принимать произвольное количество последовательностей, а функция-преобразователь обязана принимать столько же аргументов:

x = [1, 2, 3] y = [10, 20, 30] result = map(lambda a, b: a + b, x, y) print(list(result)) # [11, 22, 33]

Типовые ошибки и анти-паттерны

  • Ожидание, что map() возвращает список в Python 3
  • Попытка фильтровать внутри map() — map только преобразует
  • Передача неравных по длине последовательностей — линии обрезаются по минимальной длине

Пример из жизни

Негативный кейс

Разработчик ожидает получить список сразу:

mapped = map(abs, [-1, -2, -3]) print(mapped[1]) # TypeError: 'map' object is not subscriptable

Плюсы:

  • Экономит память

Минусы:

  • Ошибка при попытке обратиться по индексу
  • Требуется преобразование в list()

Позитивный кейс

Использование генераторного выражения для фильтрации и map() для преобразования:

nums = range(-5, 6) positives = (x for x in nums if x > 0) sq = map(lambda n: n * n, positives) print(list(sq)) # [1, 4, 9, 16, 25, 36]

Плюсы:

  • Просто комбинировать с фильтрами
  • Минимальное потребление памяти

Минусы:

  • Меньшая читаемость для новичков
  • Нет синтаксической поддержки условия