Использование функции enumerate() в Python программах
Будучи хомо сапиенсами и следуя зову врождённой способности к аналитике, мы с вами постоянно ищем различные закономерности в окружающей нас действительности и, уже по ним пытаемся объединить, сгруппировать, связать, агрегировать и еще каким-то образом синдицировать все, что только может быть типизировано и систематизировано. В итоге, фактически все, что нас окружает, имеет свой вид, тип, класс, серию или еще какую-нибудь характеристику, приобщающую его к той или иной группе (коллекции) вещей.
Все то же самое касается и, моделируемого нами при разработке Python программ, виртуального цифрового мира, который, по сути, является полным аналогом нашей с вами материальной действительности. Но, если в окружающей нас действительности отсутствует универсальный доступ к отдельным элементам нами же сформированных коллекций вещей, то в программном мире Python инструкций, есть вполне конкретный, оптимизированный для такого доступа, универсальный инструментарий.
Как раз об этом инструментарии в виде встроенной в Python функции enumerate() и пойдет речь в этой статье. Здесь нами, в частности, будет рассмотрен принцип работы этой функции, то, где она может использоваться в программном коде, какие объекты данная функция создает и какой у нее синтаксис. Кроме того, в этой же статье мы с вами разберем несколько примеров перечислений для различных типов структур данных в Python, проанализируем альтернативы для функции enumerate() и разъясним, то почему данные альтернативы менее пригодны к использованию в кодах наших программ.
Зачем в Python нужна функция enumerate() и, когда она используется?
Функция enumerate() представляет собой встроенную функцию Python, которая преобразует так называемый итерируемый объект в еще более загадочный перечисляемый одноразовый объект. Давайте же сначала разберемся, что обозначают все эти таинственные термины.
Под термином итерируемый или перебираемый объект в программировании обычно подразумеваются те структуры данных, к каждому элементу которых может быть осуществлен доступ через их поэлементный перебор в цикле. В Python к таким итерируемым объектам (структурам данных) относят любую последовательную коллекцию элементов в виде списков, кортежей, словарей или строк. Когда такой объект перерабатывается под «капотом» функции enumerate(), то каждому его элементу присваивается своеобразный порядковый номер (индекс), значительно упрощающий последующее осуществление программного доступа к любому нужному нам элементу вышеназванного объекта, который теперь уже становится перечисляемым. В частности, этот полученный от enumerate() перечисляемый объект с индексами мы можем не только преобразовывать в список, кортеж или словарь за счет соответствующих функций list(), tuple() или dict(), но и обрабатывать в цикле с тем, чтобы упростить доступ к нужным нам элементам в этом объекте через их индексы.
Кроме этого, следует отметить, что вышеназванный перечисляемый объект становиться еще и одноразовым объектом – то есть таким, все элементы которого полностью исчерпываются в процессе их перебора (прохождения цикла). Таким образом, элементы данного объекта являются доступными лишь до тех пор, пока к ним не был получен доступ (осуществлена итерация в цикле), по соответствующему их индексу в перечисляемом объекте. При последующих же итерациях они просто удаляются из памяти, максимально оптимизируя тем самым потребление вычислительных ресурсов и увеличивая скорость выполнения, разрабатываемых нами программ.
Синтаксис функции
Функция enumerate() предусматривает чрезвычайно простой синтаксис, в котором задействовано лишь два аргумента:
enumerate(iterable, start=0)
Первый аргумент iterable, предназначен для приема тех, описываемых раннее, итерируемых структур данных, которые мы хотим преобразовать через функцию enumerate() в соответствующие перечисляемые объекты, элементы которых можно перебирать, за счет добавления к ним индексов. Вторым же аргументом нашей функции является необязательный поименованный параметр start, определяющий начальное значение, с которого должна будет начинаться индексация элементов в возвращаемом этой функцией перечисляемом объекте. По умолчанию, аргумент start инициализируется в типичное для Python последовательностей значение счетчика, равное 0 (нулю).
Примечание: Нам следует иметь в виду, что применение функции enumerate() к какой-либо итерируемой структуре данных не изменяет саму эту исходный структуру. Поэтому, при необходимости дальнейшего использования, преобразованного из структуры данных перечисляемого объекта, нам нужно будет этот, являющейся результатом функции enumerate() объект, присвоить к конкретной переменной.
Теперь же, давайте с вами рассмотрим, как работает наша функция enumerate() с различными типами структур данных (итерируемых объектов) Python.
Особенности применения enumerate() к различным типам структур данных
Работа функции со списками
Первым делом, давайте разберемся, как же работает функция enumerate() со списками Python:
napitki = ['чай', 'кофе', 'капучино', 'лимонад', 'узвар']
perebor_napitkov = enumerate(napitki)
print(type(perebor_napitkov))
print(perebor_napitkov)
Результаты запуска кода:
<class 'enumerate'>
<enumerate object at 0x000002C373AD70C0>
Как мы видим, по результатам запуска данного кода, тип полученного нами объекта имеет то же название enumerate, что и сама сформировавшая его функция. В то же время, при попытке распечатать этот перечисляемый объект вместо того, чтобы получить доступ к его элементам, мы просто еще раз подтверждаем тип данного объекта и выводим информацию о месте его хранения в памяти компьютера. Но, возможно, нам все-же удастся получить доступ к отдельным элементам этого объекта иным способом? Давайте попробуем осуществить такой задуманный нами план, попытавшись достучаться до первого элемента нашего объекта точно так же, как мы сделали бы это со стандартным списком или кортежем Python:
print(perebor_napitkov[0])
Результаты запуска кода:
Traceback (most recent call last):
File "…\...*.py", …, in <module>
print(perebor_napitkov[0])
TypeError: 'enumerate' object is not subscriptable
Но, как видно из примера выше, в результате данного действа мы получили ошибку, свидетельствующую о том, что элементы соответствующего перечисляемого объекта недоступны через обычный способ, срабатывающий для элементов стандартного списка. Поэтому, для получения доступа к элементам вышеназванного объекта нам нужно либо преобразовать его в список, кортеж или словарь, либо же перебрать данный объект в тривиальном цикле *for. Для начала, попробуем преобразовать наш перечисляемый объект в список:
napitki = ['чай', 'кофе', 'капучино', 'лимонад', 'узвар']
perebor_napitkov = enumerate(napitki)
spisok_perebor_napitkov = list(perebor_napitkov)
print(spisok_perebor_napitkov)
Результаты запуска кода:
[(0, 'чай'), (1, 'кофе'), (2, 'капучино'), (3, 'лимонад'), (4, 'узвар')]
Теперь нам прекрасно видна внутренняя структура результирующего списка, где каждый компонент представляет собой двухэлементный кортеж, в котором первый его элемент является индексом для соответствующего элемента в списке napitki, а второй – имеет значение непосредственно самого элемента этого исходного списка. Благодаря такой структуре полученного нами результирующего списка, для доступа к его элементам мы можем использовать общепринятый в Python, подход, основывающейся на применении индексации:
print(spisok_perebor_napitkov[0])
print(spisok_perebor_napitkov[0][1])
Результаты запуска кода:
(0, 'чай')
чай
Теперь попробуем преобразовать наш, созданный из исходного списка napitki, перечисляемый объект в такую стандартную Python структуру данных, как кортеж. Кроме того, в нашей функции enumerate() на этот раз мы еще и попытаемся приравнять необязательный параметр start к 1:
napitki = ['чай', 'кофе', 'капучино', 'лимонад', 'узвар']
kortezh_perebor_napitkov = tuple(enumerate(napitki, start=1))
print(kortezh_perebor_napitkov)
Результаты запуска кода:
((1, 'чай'), (2, 'кофе'), (3, 'капучино'), (4, 'лимонад'), (5, 'узвар'))
Внутренняя структура приведенного выше кортежа идентична структуре списка, созданного нами ранее. Однако, в результате того, что мы поменяли стартовое значение индексного счетчика, индексы в нашем результирующем кортежи приобрели более человеческий вид и, начинаются уже не со стандартного пайтоновского нуля, а с единицы.
К полученному нами в результате выполнения функции enumerate() перечисляемому объекту может быть также применен один весьма интересный метод next(). Давайте попробуем этот метод в действии:
1 2 3 4 5 6 7 8 |
|
Результаты запуска кода:
(0, 'чай')
(1, 'кофе')
(2, 'капучино')
(3, 'лимонад')
(4, 'узвар')
Traceback (most recent call last):
File "…\...*.py", line 8, in <module>
print(next(perebor_napitkov))
StopIteration
Как мы видим, каждый раз, когда для перечисляемого объекта вызывается метод next(), он возвращает из него следующий элемент в виде уже знакомого нам двухэлементного кортежа, состоящего из счетчика элемента и самого значения соответствующего элемента, взятого из исходного списка. Но, при этом, каждый вызов метода next() удаляет из нашего перечисляемого объекта соответствующий элемент и, таким образом, при шестом вызове вышеназванного метода наш объект enumerate становится исчерпанным, что приводит к выдаче ошибки StopIteration.
Действительно, если, к примеру, после вызова метода next() для последнего (пятого) элемента нашего перечисляемого объекта, попытаться преобразовать этот объект в список, то мы увидим, что он пуст:
8 |
|
Результаты запуска кода:
[]
Таким образом, при каждом применении метода next() к перечисляемому объекту, как уже упоминалось выше, из него изымается один (текущий на этот момент) элемент. Поэтому с каждым разом наш перечисляемый и, теперь уж точно одноразовый, объект становится меньше, вплоть до полного своего исчерпания. Эту же особенность, возвращаемых функцией enumerate() перечисляемых объектов мы с вами также увидим чуть попозже, когда будем обсуждать применение к данным объектам циклов for.
Применение enumerate() к кортежам
Применение функции enumerate() к кортежам абсолютно идентично ее применению к спискам:
napitki = ('чай', 'кофе', 'капучино', 'лимонад', 'узвар')
perebor_napitkov = enumerate(napitki)
print(perebor_napitkov)
Результаты запуска кода:
<enumerate object at 0x00000268D5C06FC0>
Как видно из вышеприведенного примера, мы с вами опять получили все тот же перечисляемый объект, элементы которого недоступны через обычную Python индексацию. Поэтому, нам снова придется преобразовать полученный от функции enumerate() перечисляемый объект к одной из стандартных структур данных Python. Но, на этот раз, давайте преобразуем наш результирующий объект не в список, а в словарь:
slovar_perebor_napitkov = dict(perebor_napitkov)
print(slovar_perebor_napitkov)
Результаты запуска кода:
{0: 'чай', 1: 'кофе', 2: 'капучино', 3: 'лимонад', 4: 'узвар'}
Как мы знаем, каждый элемент стандартных Python словарей представляет собой пару типа ключ: значение. В нашем случае, каждая пара результирующего словаря состоит из ключа, выступающего индексом элемента из нашего исходного кортежа, и значения, являющегося значением соответствующего элемента из все того же исходного кортежа. Таким образом, мы можем получить доступ к значениям нашего словаря обычным для Python способом:
print(slovar_perebor_napitkov[0])
print(slovar_perebor_napitkov[4])
Результаты запуска кода:
чай
узвар
Использование функции перечисления в отношении строк
Строки в Python являются такими же перебираемыми (итерируемыми) структурами данных, как уже рассматриваемые в этой статье списки, кортежи или словари. Но, если в этих структурах данных перебираются их элементы, то в строках перебираются символы, из которых они состоят. Таким образом, с помощью функции enumerate() из строк можно создать абсолютно такой же перечисляемый объект, как мы это уже делали с вами с вышеупомянутыми структурами данных:
perebor_privet_mir = enumerate('ПРИВЕТ МИР!', start=10)
print(perebor_privet_mir)
Результаты запуска кода:
<enumerate object at 0x0000027A08C47440>
Как и раннее, давайте преобразуем полученный нами перечисляемый объект perebor_privet_mir
в список:
perebor_privet_mir = enumerate('ПРИВЕТ МИР!', start=10)
spisok_perebor_privet_mir = list(perebor_privet_mir)
print(spisok_perebor_privet_mir)
Результаты запуска кода:
[(10, 'П'), (11, 'Р'), (12, 'И'), (13, 'В'), (14, 'Е'), (15, 'Т'), (16, ' '), (17, 'М'), (18, 'И'), (19, 'Р'), (20, '!')]
Из результата запуска вышеприведенного кода мы видим уже знакомые нам двухэлементные кортежи, в которых первые элементы соответствуют порядковому номеру символа в строке (начиная со стартового индексного значения, заданного нами в 10), а вторые — содержат непосредственные символы строки. В результирующем списке кортежей также учитываются пробелы и знаки препинания, которые попадаются в нашей исходной строке.
Теперь, давайте заново, но на этот раз без применения параметра start=10
, создадим объект perebor_privet_mir, который в последующем используем для перебора с помощью уже знакомого нам метода next():
perebor_privet_mir = enumerate('ПРИВЕТ МИР!')
print(next(perebor_privet_mir))
print(next(perebor_privet_mir))
print(next(perebor_privet_mir))
print(next(perebor_privet_mir))
print(next(perebor_privet_mir))
print(next(perebor_privet_mir))
# Создание списка из оставшейся части перечисляемого объекта
print(list(perebor_privet_mir))
Результаты запуска кода:
(0, 'П')
(1, 'Р')
(2, 'И')
(3, 'В')
(4, 'Е')
(5, 'Т')
[(6, ' '), (7, 'М'), (8, 'И'), (9, 'Р'), (10, '!')]
В последней строке приведенного выше фрагмента кода мы прекратили применение метода next() к перечисляемому объекту perebor_privet_mir и преобразовали этот же объект в список с помощью соответствующей функции конструктора list(). При чем, по приведенным результатам запуска кода видно, что полученный в результате этого преобразования список содержит лишь те символы исходного перечисляемого объекта, которые остались у него после многократного применения к данному объекту метода next().
Использование функции enumerate() в циклах for
Вот мы и дошли до рассмотрение, пожалуй, наиболее распространенного варианта использования функции enumerate() – ее применения в циклах for. Давайте же посмотрим с вами, как работает данная функция в сочетании с этими циклами:
for element in enumerate(['чай', 'кофе', 'капучино', 'лимонад', 'узвар']):
print(element)
Результаты запуска кода:
(0, 'чай')
(1, 'кофе')
(2, 'капучино')
(3, 'лимонад')
(4, 'узвар')
При каждом повторении (итерации) вышеприведенного цикла, точно так же как это происходило при использовании метода next(), нам возвращается соответствующий двухэлементный кортеж с значениями, представленными в виде структуры: счетчик – элемент*. Кроме этого, хотя это и не очевидно из вышеприведенного фрагмента кода, наш перечисляемый объект на каждом повторении (итерации) цикла, один за другим теряет свои элементы (т. е. двухэлементные кортежи). Чтобы продемонстрировать вышеописанную особенность, давайте перепишем приведенный выше фрагмент кода в другом виде и проверим длину результирующего списка после завершения всех итераций:
napitki = ['чай', 'кофе', 'капучино', 'лимонад', 'узвар']
perebor_napitkov = enumerate(napitki)
for element in perebor_napitkov:
print(element)
print(list(perebor_napitkov))
Результаты запуска кода:
(0, 'чай')
(1, 'кофе')
(2, 'капучино')
(3, 'лимонад')
(4, 'узвар')
[]
Как и в случае с применением метода next(), итерации по перечисляемому объекту при помощи for циклов постепенно истощают его.
Однако, возвращаясь к нашему предыдущему фрагменту кода, давайте зададимся вопросом, как с помощью цикла мы можем получить доступ к каждому отдельному элементу перечисляемого объекта внутри имеющихся в нем двухэлементных кортежей? С целью ответа на этот вопрос, давайте в нижеприведенном коде добавим еще одну итерационную переменную, за счет чего осуществим распаковку кортежей:
for indeks, napitok in enumerate(['чай', 'кофе', 'капучино', 'лимонад', 'узвар']):
print(indeks, napitok)
Результаты запуска кода:
0 чай
1 кофе
2 капучино
3 лимонад
4 узвар
В вышеприведенном примере мы распаковываем каждый кортеж, возвращаемый из перечисляемого объекта, в две переменные, первая из которых indeks является порядковым номером (индексом) элемента, а вторая napitok представляет собой непосредственное значение соответствующего элемента в вышеназванном объекте.
Следует обратить внимание, что тот же результат, который будет абсолютно идентичен вышеприведенному, мы можем реализовать и без использования функции enumerate(). Для этого, к примеру, мы можем ввести переменную-счетчик вне цикла и увеличивать ее на 1 при каждой итерации цикла:
indeks = 0
for napitok in ['чай', 'кофе', 'капучино', 'лимонад', 'узвар']:
print(indeks, napitok)
indeks += 1
Результаты запуска кода:
0 чай
1 кофе
2 капучино
3 лимонад
4 узвар
В качестве еще одной альтернативы enumerate() мы можем использовать комбинацию встроенных в Python функций range() и len(), применяемых для обработки предварительно определенных структур данных на подобие списков, кортежей, строк и т. п.:
napitki = ['чай', 'кофе', 'капучино', 'лимонад', 'узвар']
for indeks in range(len(napitki)):
print(indeks, napitki[indeks])
Результаты запуска кода:
0 чай
1 кофе
2 капучино
3 лимонад
4 узвар
Более того, для обоих приведенных выше вариантов обработки структур данных без использования функции enumerate() мы по-прежнему можем настроить переменную indeks так, чтобы она начиналась не с нуля:
indeks = 1
for napitok in ['чай', 'кофе', 'капучино', 'лимонад', 'узвар']:
print(indeks, napitok)
indeks += 1
print('------------')
napitki = ['чай', 'кофе', 'капучино', 'лимонад', 'узвар']
for indeks in range(len(napitki)):
print(indeks + 1, napitki[indeks])
Результаты запуска кода:
1 чай
2 кофе
3 капучино
4 лимонад
5 узвар
------------
1 чай
2 кофе
3 капучино
4 лимонад
5 узвар
Однако в сравнении с применением функции enumerate() оба вышеописанных подхода на самом деле оказываются гораздо более неуклюжими, подверженными ошибкам (к примеру, мы можем просто забыть задать в цикле строку для обновления счетчика индекса при каждой итерации), а также требующими большего количества кода. Следовательно, использование функции enumerate() представляет собой наиболее оптимальный универсализированный для Python подход, способный обеспечить доступ, как к индексам элементов, так и к их непосредственным значениям при соответствующих итерациях любых стандартных для этого языка структур данных.
Выводы
В этой статье мы с вами познакомились с функцией enumerate(), являющейся одним из самых оптимизированных, мощных и универсализированных инструментариев для доступа к элементам стандартных структур данных Python. В частности, благодаря этой статье мы узнали о многочисленных вариантах применения вышеназванной функции, об ее альтернативах и о том, почему в конечном итоге эти альтернативы все же уступают данной функции. Не смотря на свою универсальность и широкий ассортимент возможных вариантов применения, функция enumerate() знакома далеко не всем начинающим пользователям Python. В некоторых же случаях об этой невероятно полезной функции могут быть не осведомлены и иные, даже весьма продвинутые программисты. Именно поэтому, очень хочется надеяться, что данная статья послужит существенным активатором для новых идей и начинаний в разработках не только тех, кто лишь стартует на нелегком, но завораживающем попроще программирования, но и тех, для кого мир Python уже стал неразрывной частью их бытия.
Возможно будет интересно
🏆 Hello, world!
Мы вчера запустили новый www.pylot.me. Должны были в следующую среду, но запустили вчера.
Как практиковаться в Python?
Для улучшения качества знаний и повышения уровня программиста, необходим постоянный практикум. Где можно это организовать самостоятельно, и как практиковаться в Python?
Условные конструкции и сопоставление структурных шаблонов
Шпаргалка по условным конструкциям и сопоставлению структурных шаблонов