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
# Вика: 78zip работает с любым количеством итерируемых — двумя, тремя, десятью.
Разная длина — 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 — когда длины обязаны совпадать и несовпадение означает баг (например, колонки и значения одной строки таблицы).