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

Содержание статьи
Синтаксис и что возвращает enumerate()
Функция встроенная и принимает два аргумента:
enumerate(iterable, start=0)iterableэто любой перебираемый объект: список, кортеж, строка, словарь, множество, файл, генератор.startзадает число, с которого начинается счет. По умолчанию 0. Значение должно быть целым, дробное вызоветTypeError.
Результат это ленивый итератор, который на каждом шаге отдает кортеж (номер, элемент). Чтобы увидеть все пары сразу, его превращают в список.
languages = ['Python', 'Java', 'Go']
pairs = enumerate(languages)
print(pairs) # <enumerate object at 0x...>
print(list(pairs)) # [(0, 'Python'), (1, 'Java'), (2, 'Go')]
print(list(pairs)) # [] итератор уже пройденКак и другие итераторы, объект enumerate одноразовый: после полного прохода он пуст. Если пары нужны несколько раз, сохраните их в список или вызовите enumerate() заново.
Нумерация в цикле for
Основной сценарий: перебрать коллекцию и одновременно знать номер текущего элемента. Кортеж сразу распаковывается в две переменные. Как устроены распаковка и сам цикл, описано в статье про цикл for.
grocery = ['хлеб', 'молоко', 'масло']
for index, item in enumerate(grocery):
print(index, item)
# 0 хлеб
# 1 молоко
# 2 маслоСчет с единицы
Для вывода пользователю нумерация с нуля выглядит странно. Параметр start сдвигает только номер, элементы и их настоящие индексы в списке не меняются.
tasks = ['Купить продукты', 'Позвонить маме', 'Сдать отчет']
for num, task in enumerate(tasks, start=1):
print(f"{num}. {task}")
# 1. Купить продукты
# 2. Позвонить маме
# 3. Сдать отчетПочему не range(len())
Тот же результат можно получить через for i in range(len(tasks)) и обращение tasks[i]. Такой код длиннее, хуже читается и работает только с коллекциями, у которых есть индексы и длина. enumerate() подходит для любых итерируемых объектов, включая генераторы и файлы, у которых len() нет.
| Задача | Вариант с range(len()) | Вариант с enumerate() |
|---|---|---|
| Номер и элемент | for i in range(len(a)): x = a[i] | for i, x in enumerate(a): |
| Нумерация с 1 | print(i + 1, a[i]) | enumerate(a, start=1) |
| Генератор или файл | Не работает | Работает |
Вариант с range(len()) оправдан, когда сам элемент не нужен, а нужны только позиции, например при обмене соседних элементов. Но и для сравнения пар соседей часто короче записать zip(a, a[1:]).
Изменение элементов списка по индексу
Переменная цикла это копия ссылки, и присваивание ей нового значения список не меняет. Чтобы заменить элемент на месте, нужен индекс, и enumerate() его дает.
prices = [100, 250, 80, 400]
for i, price in enumerate(prices):
if price > 200:
prices[i] = round(price * 0.9)
print(prices) # [100, 225, 80, 360]Не удаляйте элементы из списка во время такого перебора: индексы сдвигаются, и часть элементов будет пропущена. Для отбора лучше построить новый список.
Строки, словари и файлы
Функция работает с любой коллекцией, но результат зависит от того, что именно отдает объект при переборе.
word = 'мама'
print([i for i, ch in enumerate(word) if ch == 'а']) # [1, 3]
scores = {'Анна': 92, 'Борис': 78}
for place, (name, score) in enumerate(scores.items(), start=1):
print(place, name, score)
# 1 Анна 92
# 2 Борис 78У словаря без items() перебираются только ключи. Обратите внимание на скобки при распаковке: пара из items() вложена в кортеж от enumerate(), поэтому пишем place, (name, score).
При чтении файла построчно номер строки помогает в сообщениях об ошибках. Файл не загружается в память целиком, строки читаются по одной. Подробнее о режимах открытия написано в материале о чтении и записи файлов.
with open('config.txt', encoding='utf-8') as f:
for line_no, line in enumerate(f, start=1):
if '=' not in line:
print(f"Строка {line_no}: нет знака равенства")Связка с zip() и генераторами
Когда нужно идти по двум спискам сразу и знать номер шага, zip() вкладывают в enumerate(). Подробно о параллельном переборе рассказано в статье про zip и map.
names = ['Анна', 'Борис', 'Вера']
ages = [25, 31, 28]
for i, (name, age) in enumerate(zip(names, ages), start=1):
print(i, name, age)
# 1 Анна 25
# 2 Борис 31
# 3 Вера 28
menu = dict(enumerate(['Новая игра', 'Загрузить', 'Выход'], start=1))
print(menu) # {1: 'Новая игра', 2: 'Загрузить', 3: 'Выход'}
index_of = {name: i for i, name in enumerate(names)}
print(index_of['Вера']) # 2Последний пример строит обратный индекс: по значению сразу получаем позицию, не вызывая медленный list.index() в цикле. Такие однострочники строятся по правилам генераторов, которые разобраны в статье про list comprehension. Полное описание функции есть в документации Python.
Частые вопросы
Ответы на вопросы, которые возникают при использовании enumerate().
Как начать нумерацию с 1?
Передайте второй аргумент: enumerate(items, 1) или enumerate(items, start=1). Именованная форма читается понятнее.
Как перебрать список с конца с правильными индексами?
Оберните результат в reversed(list(enumerate(items))). Сам объект enumerate не поддерживает reversed(), поэтому его сначала превращают в список.
Можно ли получить элемент по номеру из enumerate()?
Нет, это итератор без индексации, запись enumerate(a)[0] вызовет TypeError. Для доступа по номеру обращайтесь к исходному списку.
Замедляет ли enumerate() цикл?
Разница с ручным счетчиком минимальна, а по сравнению с range(len()) и обращением по индексу enumerate() обычно даже немного быстрее.