enumerate и zip в Python — итерация с индексом и параллельный обход

Коротко

enumerate добавляет счётчик к итерации, zip обходит несколько последовательностей параллельно. Два встроенных инструмента, которые аналитики используют ежедневно: пронумеровать строки, сопоставить названия колонок со значениями, собрать словарь из двух списков. На собеседованиях спрашивают поведение zip с разной длиной, распаковку через zip(*) и параметр start у enumerate.

enumerate — индекс + значение

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

topics = ['SQL', 'Python', 'A/B-тесты', 'Статистика']

for i, topic in enumerate(topics):
    print(f"{i}: {topic}")
# 0: SQL
# 1: Python
# 2: A/B-тесты
# 3: Статистика

Без enumerate пришлось бы вести счётчик вручную или использовать range(len(...)) — оба варианта хуже.

Параметр start

По умолчанию нумерация начинается с 0. Параметр start меняет начальное значение:

for i, topic in enumerate(topics, start=1):
    print(f"{i}. {topic}")
# 1. SQL
# 2. Python
# 3. A/B-тесты
# 4. Статистика

Удобно для нумерации с единицы — в отчётах, логах, выводе для пользователя.

zip — параллельный обход

zip принимает несколько итерируемых и на каждой итерации возвращает кортеж из соответствующих элементов:

names = ['Анна', 'Борис', 'Вика']
scores = [85, 92, 78]

for name, score in zip(names, scores):
    print(f"{name}: {score}")
# Анна: 85
# Борис: 92
# Вика: 78

zip работает с любым количеством итерируемых — двумя, тремя, десятью.

Разная длина — zip останавливается по кратчайшему

Если длины не совпадают, zip молча обрезает по самой короткой последовательности:

a = [1, 2, 3, 4, 5]
b = ['x', 'y', 'z']

list(zip(a, b))
# [(1, 'x'), (2, 'y'), (3, 'z')]  — элементы 4 и 5 потеряны

Это частый источник багов. Если потеря данных недопустима — используйте zip_longest.

zip_longest из itertools

zip_longest дополняет короткие последовательности значением fillvalue (по умолчанию None):

from itertools import zip_longest

a = [1, 2, 3, 4, 5]
b = ['x', 'y', 'z']

list(zip_longest(a, b, fillvalue='-'))
# [(1, 'x'), (2, 'y'), (3, 'z'), (4, '-'), (5, '-')]

Распаковка через zip(*)

zip(*) — идиома для «развернуть» список кортежей обратно в отдельные списки:

pairs = [(1, 'a'), (2, 'b'), (3, 'c')]

numbers, letters = zip(*pairs)
# numbers = (1, 2, 3)
# letters = ('a', 'b', 'c')

*pairs распаковывает список в аргументы: zip((1, 'a'), (2, 'b'), (3, 'c')), и zip «транспонирует» их. Результат — кортежи, не списки. Если нужны списки — оберните в list() или используйте list comprehension.

Примеры для аналитика

# Сопоставить названия колонок со значениями
columns = ['user_id', 'event', 'timestamp']
values = [42, 'purchase', '2026-04-09']
row = dict(zip(columns, values))
# {'user_id': 42, 'event': 'purchase', 'timestamp': '2026-04-09'}

# Прогресс-счётчик при обработке файлов
files = ['data_jan.csv', 'data_feb.csv', 'data_mar.csv']
for i, f in enumerate(files, start=1):
    print(f"Обработка {i}/{len(files)}: {f}")

# Объединить два списка в словарь
metric_names = ['retention_d1', 'retention_d7', 'retention_d30']
metric_values = [0.45, 0.28, 0.12]
metrics = dict(zip(metric_names, metric_values))
# {'retention_d1': 0.45, 'retention_d7': 0.28, 'retention_d30': 0.12}

# enumerate + zip — индекс при параллельном обходе
names = ['Анна', 'Борис']
scores = [85, 92]
for i, (name, score) in enumerate(zip(names, scores)):
    print(f"{i}: {name} — {score}")

Частые ошибки

Модификация списка во время enumerate. Удаление или добавление элементов при итерации ломает индексацию. Итерируйте по копии или собирайте индексы для удаления отдельно.

# Баг — пропускает элементы
items = [1, 2, 3, 4, 5]
for i, x in enumerate(items):
    if x % 2 == 0:
        items.pop(i)  # Не делайте так

# Правильно — через list comprehension
items = [x for x in items if x % 2 != 0]

Забыть, что zip обрезает. Если списки должны быть одной длины — проверяйте заранее или используйте zip(..., strict=True) (Python 3.10+), который бросит ValueError при несовпадении длин.

Вопросы с собеседований

-- Что делает enumerate? -- Оборачивает итерируемый объект и на каждой итерации возвращает кортеж (индекс, элемент). Принимает необязательный параметр start для начального значения счётчика. Возвращает ленивый итератор — enumerate-объект.

-- Что произойдёт, если передать в zip списки разной длины? -- zip остановится по самому короткому списку. Лишние элементы длинных списков игнорируются без предупреждения. Чтобы дополнить короткие — itertools.zip_longest. Чтобы получить ошибку — zip(..., strict=True) в Python 3.10+.

-- Как «развернуть» список кортежей обратно в отдельные списки? -- Через zip(*). Пример: a, b = zip(*[(1, 'x'), (2, 'y')]) даст a = (1, 2) и b = ('x', 'y'). Оператор * распаковывает список в аргументы zip, и zip транспонирует их.

-- Чем for i in range(len(lst)) хуже for i, x in enumerate(lst)? -- enumerate — идиоматичный Python. Не нужен отдельный доступ по индексу lst[i], меньше шансов на ошибку, лучше читаемость. range(len(...)) — C-стиль, в Python так не принято.

-- Что вернёт dict(zip(['a', 'b', 'a'], [1, 2, 3]))? -- {'a': 3, 'b': 2}. Ключ 'a' встречается дважды — побеждает последнее значение (3).

Потренировать Python-вопросы на практике можно в тренажёре Карьерника. Подробнее про list comprehension и lambda-функции. А больше примеров вопросов — на отдельной странице.

Открыть тренажёр в Telegram — вопросы по Python, pandas, SQL и аналитике. Бесплатно.

FAQ

enumerate возвращает список или итератор?

Итератор (enumerate-объект). Элементы вычисляются лениво, по одному. Если нужен список кортежей — оберните в list(enumerate(...)). Но обычно enumerate используют прямо в цикле for, и оборачивать не нужно.

Можно ли использовать zip с генераторами?

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

Когда использовать zip_longest, а когда strict=True?

zip_longest — когда разная длина ожидаема и нужно дополнить пропуски (например, объединение данных из разных источников). strict=True — когда длины обязаны совпадать и несовпадение означает баг (например, колонки и значения одной строки таблицы).