История вопроса:
Функция enumerate() появилась в Python 2.3 и сейчас это стандартный способ параллельно получать доступ к элементу и индексу элемента при переборе коллекций. До появления enumerate() программисты часто создавали собственные счётчики или использовали функцию range(len(sequence)), что было неудобно и нечитабельно.
Проблема:
Обычный цикл for перебирает только значения. Для доступа к индексу часто используют range(len(...)), что работает не для всех итерируемых объектов (например, генераторов, строк и кортежей изменяемой длины, а также при фильтрации). Это ведёт к ошибкам и усложняет код.
Решение:
enumerate() возвращает пары (индекс, элемент), что позволяет получить индекс текущего элемента даже для нестандартных коллекций или фильтрованных генераторов. Функция принимает второй необязательный аргумент — стартовое значение счётчика.
Пример кода:
words = ['apple', 'banana', 'cherry'] for idx, word in enumerate(words, 1): print(f"{idx}: {word}") # Вывод: # 1: apple # 2: banana # 3: cherry
Ключевые особенности:
Зачем в функциях часто используют enumerate вместо range(len(seq))?
Ответ: range(len(seq)) работает только для последовательностей с доступом по индексу и не учитывает изменения длины во время итерации. Кроме того, он хуже читается и работает медленнее или не работает вовсе для генераторов. enumerate() даёт безопасный доступ к парам индекс-значение для любой итерируемой коллекции.
Пример кода:
# Не работает с генератором: gen = (x for x in range(5)) for i in range(len(gen)): print(i) # Ошибка: у генератора нет длины
Можно ли использовать enumerate для изменения элементов списка в процессе обхода?
Ответ: Да, но нужно итерировать по индексам, чтобы записывать значения. Иначе, если итерироваться только по значениям, вы измените копию объекта, а не оригинал.
Пример кода:
nums = [1, 2, 3] for idx, val in enumerate(nums): nums[idx] = val * 2 # nums = [2, 4, 6]
Что вернёт enumerate, если передать ему объект, изменяющийся по ходу итерации?
Ответ: Если коллекция изменяется в процессе обхода (например, удаляются элементы), поведение может быть неожиданным, потому что enumerate идёт по внутреннему итератору, который может сбиться. Поэтому изменять коллекцию при итерации не рекомендуется.
range(len(...)) для объектов без длины или для тех, у кого длина может измениться.Негативный кейс
Программист перебирает список, используя range(len(list)), и удаляет элементы на ходу. Итог — индексы сбиваются, часть элементов пропускается.
Плюсы:
Минусы:
Позитивный кейс
Используется enumerate(), при этом формируется новый список нужных элементов или изменяется значение по индексу, но размер списка не меняется в цикле.
Плюсы:
Минусы: