enumerate() в Python: индекс и значение в цикле for
Как работает enumerate в Python: индекс и значение в цикле for, параметр start, словари, строки и файлы, zip + enumerate и частые ошибки новичков.
Содержание · 10 разделов
- Синтаксис enumerate
- Распаковка индекса и значения
- Параметр start: нумерация с единицы
- enumerate против range(len())
- Изменение элементов списка по индексу
- enumerate со словарями, строками и файлами
- Словарь
- Строка
- Файл
- Превращение в list и dict
- zip и enumerate вместе
- Частые ошибки
- ValueError: too many values to unpack
- Перепутан порядок переменных
- TypeError: object of type ‘enumerate’ has no len()
- Повторный обход пустого enumerate
- list.index() вместо enumerate
- Ожидание, что start пропустит элементы
- Частые вопросы
- Можно ли задать шаг нумерации?
- Как обратиться к соседнему элементу при обходе?
- Как пронумеровать элементы в шаблоне Django?
- Как найти индекс и значение максимального элемента?
- Итог
enumerate() в Python — встроенная функция, которая при обходе коллекции выдаёт не только элемент, но и его порядковый номер. Это стандартный способ получить индекс в цикле for без range(len(...)) и ручного счётчика.
Быстрый ответ:
fruits = ["яблоко", "банан", "вишня"]
for i, fruit in enumerate(fruits):
print(i, fruit)
# → 0 яблоко
# → 1 банан
# → 2 вишня
Нужна нумерация с единицы — передайте start=1: enumerate(fruits, start=1). Ниже — как это устроено, где пригодится и какие ошибки встречаются чаще всего. Все примеры проверены на Python 3.12.
Синтаксис enumerate
enumerate(iterable, start=0)
iterable— любой итерируемый объект: список, кортеж, строка, словарь, множество, файл, генератор.start— с какого числа начинать счёт. По умолчанию0. Должно быть целым числом.
Функция возвращает объект enumerate — ленивый итератор, который на каждом шаге отдаёт кортеж (номер, элемент):
fruits = ["яблоко", "банан", "вишня"]
print(enumerate(fruits))
# → <enumerate object at 0x0000018571D11580>
for item in enumerate(fruits):
print(item)
# → (0, 'яблоко')
# → (1, 'банан')
# → (2, 'вишня')
Адрес в выводе у вас будет другим — важно, что enumerate возвращает итератор: элементы выдаются по одному при обходе. Внутри enumerate работает примерно так же, как этот генератор:
def my_enumerate(iterable, start=0):
n = start
for elem in iterable:
yield n, elem
n += 1
Распаковка индекса и значения
Обычно кортеж сразу распаковывают в две переменные прямо в заголовке цикла: for i, fruit in enumerate(fruits). Порядок фиксирован — сначала номер, потом элемент. Имена переменных могут быть любыми, но i, idx, index, n для номера — общепринятые варианты.
Если номер не нужен, enumerate не нужен тоже — достаточно for fruit in fruits. Если нужен только номер, а элемент — нет, используйте _:
for i, _ in enumerate(fruits):
...
Хотя в этом случае проще range(len(fruits)).
Параметр start: нумерация с единицы
Пользователям показывают нумерацию с 1, а индексы в Python начинаются с 0. Параметр start решает это без i + 1 в каждой строке:
for number, fruit in enumerate(fruits, start=1):
print(f"{number}. {fruit}")
# → 1. яблоко
# → 2. банан
# → 3. вишня
start можно передать и позиционно, и любым целым числом, в том числе отрицательным:
print(list(enumerate("abc", 10))) # → [(10, 'a'), (11, 'b'), (12, 'c')]
print(list(enumerate("abc", -1))) # → [(-1, 'a'), (0, 'b'), (1, 'c')]
Важно:
startменяет только счётчик, а не то, с какого элемента начинается обход.enumerate(fruits, start=1)всё равно начнёт сfruits[0], просто присвоит ему номер 1.
Чтобы пропустить элементы — например, строку заголовка, — сделайте срез, а start используйте для правильной нумерации:
rows = ["name,age", "Анна,30", "Борис,25"]
for i, row in enumerate(rows[1:], start=1):
print(i, row)
# → 1 Анна,30
# → 2 Борис,25
enumerate против range(len())
Новички часто пишут цикл по индексам, как в C или Pascal:
for i in range(len(fruits)):
print(i, fruits[i])
Это работает, но enumerate лучше по нескольким причинам:
| Критерий | range(len(seq)) | enumerate(seq) |
|---|---|---|
| Читаемость | нужно обращаться seq[i] | элемент уже в переменной |
Работает с генераторами, файлами, set | нет: нужен len() и индексация | да |
| Нумерация с произвольного числа | i + 1 вручную | start=1 |
| Риск ошибки с индексом | выше | ниже |
range(len()) оправдан, когда элемент вам не нужен вовсе или нужно обходить индексы нестандартно (с шагом, в обратном порядке). Подробнее про range и циклы — в шпаргалке по синтаксису Python.
Изменение элементов списка по индексу
Типичная задача, где нужен индекс, — заменить элементы прямо в списке. Присваивание переменной цикла (price = ...) список не меняет, а seq[i] = ... — меняет:
prices = [100, 250, 80]
for i, price in enumerate(prices):
if price < 200:
prices[i] = price * 2
print(prices) # → [200, 250, 160]
Подробнее о методах и изменении списков — в статье про списки в Python.
enumerate со словарями, строками и файлами
Словарь
Обход словаря идёт по ключам, поэтому enumerate(d) нумерует ключи. Чтобы получить и ключ, и значение, передайте d.items() и распакуйте вложенный кортеж в скобках:
capitals = {"Россия": "Москва", "Франция": "Париж", "Япония": "Токио"}
for i, country in enumerate(capitals):
print(i, country)
# → 0 Россия
# → 1 Франция
# → 2 Япония
for i, (country, city) in enumerate(capitals.items(), start=1):
print(f"{i}. {country} — {city}")
# → 1. Россия — Москва
# → 2. Франция — Париж
# → 3. Япония — Токио
Словари сохраняют порядок вставки (с Python 3.7), так что нумерация предсказуема. Больше приёмов работы со словарями — в статье про словари в Python.
Строка
Строка итерируется посимвольно, enumerate даёт позицию каждого символа:
word = "привет"
for i, ch in enumerate(word):
if ch in "аеёиоуыэюя":
print(i, ch)
# → 2 и
# → 4 е
# все позиции символа одной строкой
print([i for i, ch in enumerate("banana") if ch == "a"]) # → [1, 3, 5]
Файл
Открытый файл — итератор по строкам, поэтому enumerate удобно использовать для номеров строк, например в сообщениях об ошибках. Пусть lines.txt содержит четыре строки, третья из которых пустая:
with open("lines.txt", encoding="utf-8") as f:
for lineno, line in enumerate(f, start=1):
line = line.rstrip("\n")
if not line:
print(f"Строка {lineno}: пустая")
else:
print(f"{lineno}: {line}")
1: host = localhost
2: port = 8000
Строка 3: пустая
4: debug = true
Файл при этом читается построчно и целиком в память не загружается.
Превращение в list и dict
Объект enumerate — итератор, поэтому его можно передать в list() или dict():
fruits = ["яблоко", "банан", "вишня"]
print(list(enumerate(fruits)))
# → [(0, 'яблоко'), (1, 'банан'), (2, 'вишня')]
print(dict(enumerate(fruits)))
# → {0: 'яблоко', 1: 'банан', 2: 'вишня'}
Часто нужен обратный словарь — «элемент → его индекс». Его строят генератором словаря:
index = {fruit: i for i, fruit in enumerate(fruits)}
print(index) # → {'яблоко': 0, 'банан': 1, 'вишня': 2}
Такой словарь находит позицию за O(1), тогда как fruits.index(x) каждый раз просматривает список целиком.
zip и enumerate вместе
zip() объединяет несколько последовательностей попарно, а enumerate добавляет к этим парам номер. Порядок важен: enumerate(zip(...)), а распаковка — с вложенными скобками:
names = ["Анна", "Борис", "Вера"]
scores = [92, 78, 85]
for place, (name, score) in enumerate(zip(names, scores), start=1):
print(place, name, score)
# → 1 Анна 92
# → 2 Борис 78
# → 3 Вера 85
Тот же приём работает и для вложенных циклов — у каждого уровня свой счётчик:
matrix = [[1, 2], [3, 4]]
for r, row in enumerate(matrix):
for c, value in enumerate(row):
print(f"[{r}][{c}] = {value}")
# → [0][0] = 1
# → [0][1] = 2
# → [1][0] = 3
# → [1][1] = 4
Если нужно не перебирать пары, а применить функцию к каждому элементу, посмотрите на функцию map().
Частые ошибки
ValueError: too many values to unpack
Забыли обернуть коллекцию в enumerate, но распаковываете в две переменные:
fruits = ["яблоко", "банан", "вишня"]
for i, fruit in fruits: # ValueError: too many values to unpack (expected 2)
...
Python пытается разложить строку "яблоко" на две переменные. Если строки однобуквенные, текст будет другим: not enough values to unpack (expected 2, got 1). Исправление одно — for i, fruit in enumerate(fruits). Как читать такие сообщения, разобрано в статье про traceback и ошибки Python.
Перепутан порядок переменных
for fruit, i in enumerate(fruits) не вызывает ошибку: в fruit окажется номер, а в i — элемент. Запомните: enumerate всегда отдаёт сначала номер.
TypeError: object of type ‘enumerate’ has no len()
Объект enumerate не поддерживает len(), индексацию и reversed():
e = enumerate(fruits)
len(e) # TypeError: object of type 'enumerate' has no len()
e[0] # TypeError: 'enumerate' object is not subscriptable
reversed(e) # TypeError: 'enumerate' object is not reversible
Для этих операций сначала превратите его в список: list(enumerate(fruits)). Обход с конца с сохранением исходных индексов:
for i, fruit in reversed(list(enumerate(fruits))):
print(i, fruit)
# → 2 вишня
# → 1 банан
# → 0 яблоко
Повторный обход пустого enumerate
Итератор одноразовый: после полного прохода он пуст.
pairs = enumerate(fruits)
print(list(pairs)) # → [(0, 'яблоко'), (1, 'банан'), (2, 'вишня')]
print(list(pairs)) # → []
Если пары нужны несколько раз, сохраните list(enumerate(...)) или вызывайте enumerate() заново.
list.index() вместо enumerate
seq.index(x) возвращает позицию первого вхождения, поэтому при повторах индексы будут неверными, а на длинных списках цикл станет квадратичным:
letters = ["a", "b", "a"]
for letter in letters:
print(letters.index(letter), letter)
# → 0 a
# → 1 b
# → 0 a ← ожидали 2
С enumerate(letters) вы получите правильные 0, 1, 2.
Ожидание, что start пропустит элементы
start=1 сдвигает только нумерацию: первый элемент всё равно попадёт в обход. Для пропуска используйте срез seq[1:] или itertools.islice().
Частые вопросы
Можно ли задать шаг нумерации?
Параметра для шага у enumerate() нет: start задаёт только первое число. Умножьте номер на шаг или объедините элементы со счётчиком itertools.count(start, step) через zip().
from itertools import count
items = ["a", "b", "c"]
print(list(zip(count(100, 10), items))) # → [(100, 'a'), (110, 'b'), (120, 'c')]
Как обратиться к соседнему элементу при обходе?
Индекс из enumerate() позволяет прочитать items[i + 1], но тогда нужно следить за границей списка. Для пар «текущий — следующий» проще zip(items, items[1:]) или itertools.pairwise() (Python 3.10+).
temps = [12, 15, 11, 18]
print([cur - prev for prev, cur in zip(temps, temps[1:])]) # → [3, -4, 7]
Как пронумеровать элементы в шаблоне Django?
Вызвать enumerate() в шаблоне нельзя, но тег for сам ведёт счётчик: forloop.counter считает с единицы, forloop.counter0 — с нуля. Остальные переменные цикла описаны в статье про шаблоны Django.
{% for fruit in fruits %}
{{ forloop.counter }}. {{ fruit }}
{% endfor %}
Как найти индекс и значение максимального элемента?
Передайте enumerate() в max() и сравнивайте пары по второму элементу. При одинаковых значениях вернётся первая подходящая пара; для минимума так же работает min().
scores = [55, 90, 47, 90]
print(max(enumerate(scores), key=lambda p: p[1])) # → (1, 90)
print(min(enumerate(scores), key=lambda p: p[1])) # → (2, 47)
Итог
enumerate(iterable, start=0) даёт пары (номер, элемент) и заменяет ручной счётчик и range(len()). Используйте start=1 для человеческой нумерации, d.items() для словарей, enumerate(zip(...)) для нескольких списков и list()/dict(), когда результат нужен целиком. Полный список встроенных функций и конструкций языка собран в шпаргалке по синтаксису Python, а закрепить циклы помогут задачи по Python с решениями. Официальное описание — в документации Python.
- #Python
- #enumerate
- #Циклы
- #for
- #Встроенные функции