W3docs

Python enumerate, zip, map и filter

Как работают встроенные функции Python enumerate, zip, map и filter: примеры и практические паттерны для написания чистого кода итерации.

Python включает четыре встроенные функции — enumerate, zip, map и filter — которые заменяют распространённые паттерны циклов одним читаемым выражением. Каждая из них возвращает итератор (ленивый, эффективно использующий память), и они естественно сочетаются с циклами for и генераторами списков.

В этой главе рассматривается:

  • enumerate() — цикл с автоматическим индексом
  • zip() — параллельный обход двух и более последовательностей
  • map() — применение функции к каждому элементу
  • filter() — сохранение только элементов, прошедших проверку
  • Комбинирование всех четырёх функций в реальных паттернах
  • Когда предпочесть генератор списков

enumerate() — индекс при обходе цикла

Когда во время цикла нужны и позиция, и значение, наивный подход использует ручной счётчик:

colors = ["red", "green", "blue"]
i = 0
for color in colors:
    print(i, color)
    i += 1

enumerate() делает это автоматически. Он оборачивает любой итерируемый объект и возвращает пары (индекс, значение):

colors = ["red", "green", "blue"]
for i, color in enumerate(colors):
    print(i, color)

Вывод:

0 red
1 green
2 blue

Начало с другого индекса

Передайте аргумент start, чтобы начать отсчёт с любого целого числа:

fruits = ["apple", "banana", "cherry"]
for i, fruit in enumerate(fruits, start=1):
    print(f"{i}. {fruit}")

Вывод:

1. apple
2. banana
3. cherry

Это полезно при формировании нумерованных списков для отображения, где нумерация с 0 выглядит непривычно для читателя.

enumerate() с условием

Поскольку enumerate() — это обычный итератор, внутри тела цикла можно добавлять проверки if:

scores = [72, 91, 55, 88, 44]
for rank, score in enumerate(scores, start=1):
    if score >= 80:
        print(f"Rank {rank}: score {score} — PASS")

Вывод:

Rank 2: score 91 — PASS
Rank 4: score 88 — PASS

Получение только индекса без enumerate()

Если нужен только индекс, подойдёт range(len(seq)) — но как только потребуется и значение, используйте enumerate(), чтобы не писать seq[i] повсюду.


zip() — параллельный обход нескольких последовательностей

zip() объединяет два и более итерируемых объекта в единый итератор кортежей, сопоставляя элементы по позициям:

names = ["Alice", "Bob", "Charlie"]
scores = [95, 87, 92]

for name, score in zip(names, scores):
    print(f"{name}: {score}")

Вывод:

Alice: 95
Bob: 87
Charlie: 92

zip() с тремя и более итерируемыми объектами

first = ["a", "b", "c"]
second = [1, 2, 3]
third = [True, False, True]

for x, y, z in zip(first, second, third):
    print(x, y, z)

Вывод:

a 1 True
b 2 False
c 3 True

Последовательности разной длины

zip() останавливается на самой короткой последовательности и молча отбрасывает оставшиеся элементы из более длинных:

short = [1, 2]
long  = [10, 20, 30, 40]

print(list(zip(short, long)))

Вывод:

[(1, 10), (2, 20)]

Значения 30 и 40 из long отбрасываются. Если нужно сохранить все элементы, используйте itertools.zip_longest из стандартной библиотеки:

from itertools import zip_longest

short = [1, 2]
long  = [10, 20, 30, 40]

print(list(zip_longest(short, long, fillvalue=0)))

Вывод:

[(1, 10), (2, 20), (0, 30), (0, 40)]

Преобразование результата zip в список или словарь

zip() возвращает ленивый итератор. Оберните его в list(), чтобы сразу получить все пары, или передайте в dict(), чтобы создать словарь из двух параллельных последовательностей:

keys   = ["name", "age", "city"]
values = ["Alice", 30, "Paris"]

record = dict(zip(keys, values))
print(record)

Вывод:

{'name': 'Alice', 'age': 30, 'city': 'Paris'}

Разупаковка с помощью zip(*...)

Трюк с оператором * «разворачивает» последовательность кортежей обратно в отдельные последовательности:

pairs = [(1, "a"), (2, "b"), (3, "c")]
numbers, letters = zip(*pairs)

print(numbers)  # (1, 2, 3)
print(letters)  # ('a', 'b', 'c')

map() — применение функции к каждому элементу

map(function, iterable) применяет function к каждому элементу и возвращает ленивый итератор результатов. Он заменяет цикл for, который создаёт новый список, преобразуя каждый элемент.

numbers = [1, 2, 3, 4, 5]
squares = map(lambda x: x ** 2, numbers)
print(list(squares))

Вывод:

[1, 4, 9, 16, 25]

map() с именованной функцией

Можно передать любой вызываемый объект — встроенную функцию, функцию, определённую через def, или lambda:

words = ["  hello  ", "  world  ", "  python  "]
stripped = list(map(str.strip, words))
print(stripped)

Вывод:

['hello', 'world', 'python']

Здесь str.strip передаётся как ссылка (без скобок). map() вызывает str.strip(word) для каждого элемента.

map() с несколькими итерируемыми объектами

Передайте дополнительные итерируемые объекты для применения функции, принимающей два и более аргументов. map() проходит по всем объектам параллельно (как zip(), останавливается на самом коротком):

a = [1, 2, 3]
b = [10, 20, 30]
totals = list(map(lambda x, y: x + y, a, b))
print(totals)

Вывод:

[11, 22, 33]

map() против генератора списков

Оба подхода допустимы. Практическое правило:

СитуацияПредпочтительно
Применение существующей именованной функцииmap() — короче, lambda не нужна
Написание встроенного выраженияГенератор списков — более читаемо
Несколько входных последовательностей параллельноmap() с несколькими итерируемыми объектами

Эквивалентные формы:

# map() with a named function
result = list(map(str.upper, ["a", "b", "c"]))

# list comprehension — same result
result = [s.upper() for s in ["a", "b", "c"]]

filter() — сохранение только элементов, соответствующих условию

filter(function, iterable) оставляет элементы, для которых function возвращает истинное значение. Возвращает ленивый итератор.

numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
evens = filter(lambda x: x % 2 == 0, numbers)
print(list(evens))

Вывод:

[2, 4, 6, 8, 10]

filter() с именованной функцией

def is_positive(n):
    return n > 0

values = [-3, -1, 0, 4, 7, -2]
positives = list(filter(is_positive, values))
print(positives)

Вывод:

[4, 7]

filter(None, iterable) — удаление ложных значений

Передача None в качестве функции сохраняет только истинные элементы. Это быстрый способ убрать пустые строки, нули и значения None:

mixed = [0, "hello", "", None, 42, False, "world"]
truthy = list(filter(None, mixed))
print(truthy)

Вывод:

['hello', 42, 'world']

filter() против генератора списков

filter() и условный генератор списков выполняют одну и ту же задачу:

numbers = range(1, 11)

# filter()
evens_f = list(filter(lambda x: x % 2 == 0, numbers))

# list comprehension — equivalent
evens_c = [x for x in numbers if x % 2 == 0]

print(evens_f)  # [2, 4, 6, 8, 10]
print(evens_c)  # [2, 4, 6, 8, 10]

Предпочитайте генератор списков, когда условие написано встроенно; используйте filter(), когда у вас уже есть хорошо именованная функция-предикат.


Комбинирование enumerate, zip, map и filter

Эти четыре функции естественно сочетаются. Вот реалистичный пример: у вас есть два параллельных списка — имена студентов и их сырые баллы; нужно вывести пронумерованный отчёт, включающий только прошедших испытание (балл >= 60), с баллами, конвертированными в буквенные оценки.

def letter_grade(score):
    if score >= 90:
        return "A"
    elif score >= 80:
        return "B"
    elif score >= 70:
        return "C"
    elif score >= 60:
        return "D"
    return "F"

names  = ["Alice", "Bob", "Carol", "Dave", "Eve"]
scores = [85, 52, 91, 67, 48]

# Step 1: pair names with scores
pairs = zip(names, scores)

# Step 2: keep only passing students
passing = filter(lambda pair: pair[1] >= 60, pairs)

# Step 3: convert scores to letter grades
graded = map(lambda pair: (pair[0], letter_grade(pair[1])), passing)

# Step 4: number the results
for rank, (name, grade) in enumerate(graded, start=1):
    print(f"{rank}. {name}: {grade}")

Вывод:

1. Alice: B
2. Carol: A
3. Dave: D

Каждый шаг создаёт итератор; ничто не материализуется в список до тех пор, пока цикл for не израсходует его. Это обеспечивает постоянный расход памяти независимо от размера входных данных.


Распространённые ошибки

Итераторы исчерпываются после одного прохода

map(), filter() и zip() возвращают итераторы, а не списки. После того как итератор потреблён, он пуст:

squares = map(lambda x: x ** 2, [1, 2, 3])
print(list(squares))  # [1, 4, 9]
print(list(squares))  # [] — already exhausted

Оберните в list() заранее, если нужно обойти данные более одного раза.

zip() молча отбрасывает конечные элементы

Когда входные последовательности имеют разную длину, zip() отбрасывает лишние элементы без предупреждения. Явно проверяйте длины, если это важно, или используйте itertools.zip_longest.

enumerate() не изменяет итерируемый объект

enumerate() оборачивает исходный итерируемый объект и не копирует его. Изменение базового списка в процессе итерации может привести к неожиданным результатам — та же осторожность, что и при обычных циклах for.


Краткий справочник

ФункцияСигнатураЧто возвращает
enumerateenumerate(iterable, start=0)Итератор кортежей (index, value)
zipzip(*iterables)Итератор кортежей, по одному элементу из каждого итерируемого объекта
mapmap(function, *iterables)Итератор результатов применения function к каждому элементу
filterfilter(function, iterable)Итератор элементов, для которых function возвращает True

Все четыре функции ленивы: они производят значения по требованию, не создавая полный список заранее.


Практика

Практика
What does enumerate(items, start=1) do when used in a for loop?
What does enumerate(items, start=1) do when used in a for loop?
Практика
What happens when you pass sequences of different lengths to zip()?
What happens when you pass sequences of different lengths to zip()?
Практика
Which statement about map() is correct?
Which statement about map() is correct?
Практика
What does filter(None, iterable) do?
What does filter(None, iterable) do?
Was this page helpful?