Django-Admin.ru

enumerate() в Python: индекс и значение в цикле for

Как работает enumerate в Python: индекс и значение в цикле for, параметр start, словари, строки и файлы, zip + enumerate и частые ошибки новичков.

Редакция Django-Admin.ru
· обновлено · 10 мин чтения

Содержание · 10 разделов
  1. Синтаксис enumerate
  2. Распаковка индекса и значения
  3. Параметр start: нумерация с единицы
  4. enumerate против range(len())
  5. Изменение элементов списка по индексу
  6. enumerate со словарями, строками и файлами
  7. Словарь
  8. Строка
  9. Файл
  10. Превращение в list и dict
  11. zip и enumerate вместе
  12. Частые ошибки
  13. ValueError: too many values to unpack
  14. Перепутан порядок переменных
  15. TypeError: object of type ‘enumerate’ has no len()
  16. Повторный обход пустого enumerate
  17. list.index() вместо enumerate
  18. Ожидание, что start пропустит элементы
  19. Частые вопросы
  20. Можно ли задать шаг нумерации?
  21. Как обратиться к соседнему элементу при обходе?
  22. Как пронумеровать элементы в шаблоне Django?
  23. Как найти индекс и значение максимального элемента?
  24. Итог

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
  • #Встроенные функции
↑ ↓ выборEnter открытьEsc закрыть