Итераторы
Contents
Итераторы#
Итераторы позволяют обойти все элементы итерируемых объектов и по всюду используются в python
.
Note
Итератор (iterator
) — это объект, который должен определить метод __next__
(т.е. реагировать на функцию next), выдавать следующий элемент коллекции по вызову этого метода и бросать исключение StopIteration, когда эти элементы исчерпались.
Стандартный итератор#
Получить стандартный итератор по коллекции можно встроенной функцией iter.
a_list = list(range(3))
iterator = iter(a_list)
print(iterator)
<list_iterator object at 0x000001DE635B6320>
В ячейке выше объявлен список a_list = [0, 1, 2]
, а далее с помощью вызова функции iter создан стандартный итератор iterator
по нему. Теперь у этого итератора можно спросить следующий элемент списка функцией next.
a = next(iterator)
print(a)
0
Первый вызов функции next
возвращает первый элемент контейнера, от которого создан контейнер.
b = next(iterator)
print(b)
1
Второй вызов функции next
— второй элемент списка.
c = next(iterator)
print(c)
2
Третий вызов — третий элемент списка.
d = next(iterator)
print(d)
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
Cell In [9], line 1
----> 1 d = next(iterator)
2 print(d)
StopIteration:
Четвертый вызов функции next
должен вернуть четвертый элемент, но т.к. список a_list
содержит всего 3 элемента, то итератор уже исчерпан и возбуждается исключение StopIteration
.
Вообще говоря, можно изменить список при итерации по нему, но желательно по возможности избегать этого.
iterator = iter(a_list)
a = next(iterator)
a_list.insert(0, 42)
b, c, d = next(iterator), next(iterator), next(iterator)
print(a, b, c, d)
print(a_list)
0 0 1 2
[42, 0, 1, 2]
Цикл for
использует итераторы для прохода по итерируемым объектам и сам вызывает функцию next
до тех пор, пока не встретит исключение StopIteration
.
Среди итерируемых объектов мы уже встречались с
все последовательности: списки, кортежи, строки,
range
и т.п.;множества, словари и все связанные с ними
view
(dict_keys
,dict_values
иdict_items
);массивы
NumPy
;таблицы и столбцы
pandas
.
Нестандартные встроенные итераторы#
В python
существуют разные итераторы, которые могут пригодиться для разных целей, например, для того чтобы пробежаться по коллекции в другом порядке.
reversed
#
Встроенная функция reversed создаёт итератор, который пробегает по коллекции в обратном порядке относительно стандартного итератора iter
.
x = [1, 2, 3]
reversed_iterator = reversed(x)
print(reversed_iterator)
print(list(reversed_iterator))
<list_reverseiterator object at 0x000001DE6483FFA0>
[3, 2, 1]
enumerate
#
Встроенная функция enumerate возвращает “нумерующий итератор”, т.е. итератор, который помимо следующего элемента контейнера возвращает ещё и его номер.
L = list("abc")
enumerated_iterator = enumerate(L)
print(L)
print(enumerated_iterator)
print(list(enumerated_iterator))
['a', 'b', 'c']
<enumerate object at 0x000001DE6513BE40>
[(0, 'a'), (1, 'b'), (2, 'c')]
Часто используется в циклах, если нужен доступ к элементам контейнера по индексу. При этом, следующая запись
for i, _ in enumerate(iterable):
...
считается предпочтительнее, чем
for i in range(len(iterable)):
...
for i, x in enumerate(L):
print(i, x)
0 a
1 b
2 c
Конечно, эти итераторы можно комбинировать.
for i, x in enumerate(reversed(L)):
print(i, x)
0 c
1 b
2 a
zip
#
Встроенная функция zip создаёт итератор по нескольким итерируемым объектам, т.е. позволяет пробежаться по нескольким контейнерам одновременно.
В случае двух контейнеров название функции zip
хорошо иллюстрирует принцип её действия.
X = "ABC"
Y = [1, 2, 3]
zip_XY = zip(X, Y)
print(zip_XY)
print(list(zip_XY))
<zip object at 0x000001DE651AAF80>
[('A', 1), ('B', 2), ('C', 3)]
В ячейке выше приведен пример с двумя коллекциями X
и Y
. В общем случае можно передавать произвольное количество коллекций в функцию zip
. Типичное её применение в цикле выглядит следующем образом.
for x1, x2, ..., xn in zip(X1, X2, ..., Xn):
...
for i, x, y in zip(range(len(X)), X, Y):
print(f"{i}: {x=}, {y=}")
0: x='A', y=1
1: x='B', y=2
2: x='C', y=3
Предыдущий цикл можно заменить конструкцией вида.
for i, (x, y) in enumerate(zip(X, Y)):
print(f"{i}: {x=}, {y=}")
0: x='A', y=1
1: x='B', y=2
2: x='C', y=3
Функция zip
может быть использована для того, чтобы пробежаться по столбцам матрицы, т.е. как бы транспонировать её.
m = [
[11, 12, 13],
[21, 22, 23],
]
for row in m:
print(row)
print()
for column in zip(*m):
print(column)
[11, 12, 13]
[21, 22, 23]
(11, 21)
(12, 22)
(13, 23)
Если коллекции внутри zip
содержат разное количество элементов, то итерация прекратится, как только закончится элементы в самом коротком из них, а функция zip_longest из модуля стандартной библиотеки itertools позволяет итерировать до упора, пока хотя бы в одной последовательности остались элементы.
Модуль itertools
#
В модуле itertools определен ещё ряд полезных итераторов. Рассмотрим несколько из них.
zip_longest
#
Если коллекции внутри zip
содержат разное количество элементов, то итерация прекратится, как только закончится элементы в самом коротком из них, а функция zip_longest из модуля itertools
позволяет итерировать до упора, пока хотя бы в одной последовательности остались элементы.
from string import ascii_letters, digits
from itertools import zip_longest
for x, y in zip(digits, ascii_letters):
print(f"{x}->{y}, ", end="")
print()
for x, y in zip_longest(digits, ascii_letters, fillvalue=float("NaN")):
print(f"{x}->{y}, ", end="")
0->a, 1->b, 2->c, 3->d, 4->e, 5->f, 6->g, 7->h, 8->i, 9->j,
0->a, 1->b, 2->c, 3->d, 4->e, 5->f, 6->g, 7->h, 8->i, 9->j, nan->k, nan->l, nan->m, nan->n, nan->o, nan->p, nan->q, nan->r, nan->s, nan->t, nan->u, nan->v, nan->w, nan->x, nan->y, nan->z, nan->A, nan->B, nan->C, nan->D, nan->E, nan->F, nan->G, nan->H, nan->I, nan->J, nan->K, nan->L, nan->M, nan->N, nan->O, nan->P, nan->Q, nan->R, nan->S, nan->T, nan->U, nan->V, nan->W, nan->X, nan->Y, nan->Z,
product
#
Итератор product позволяет итерировать по декартову (или внешнему) произведению других итерируемых объектов. В некоторых ситуациях позволяет упростить конструкции из вложенных циклов.
from itertools import product
X = [0, 1, 2]
Y = "abc"
print([(x, y) for x in X for y in Y])
print(list(product(X, Y)))
[(0, 'a'), (0, 'b'), (0, 'c'), (1, 'a'), (1, 'b'), (1, 'c'), (2, 'a'), (2, 'b'), (2, 'c')]
[(0, 'a'), (0, 'b'), (0, 'c'), (1, 'a'), (1, 'b'), (1, 'c'), (2, 'a'), (2, 'b'), (2, 'c')]
permutations
#
Итератор permutations пробегается по списку всех возможных перестановок итерируемого аргумента.
from itertools import permutations
print(list(permutations("abc")))
[('a', 'b', 'c'), ('a', 'c', 'b'), ('b', 'a', 'c'), ('b', 'c', 'a'), ('c', 'a', 'b'), ('c', 'b', 'a')]
combinations
и combinations_with_replacements
#
Метод combinations возвращает все возможные способы выбрать r
элементов итерируемого объекта без повторов.
from itertools import combinations
for x, y in combinations(range(3), 2):
print(f"{x} and {y}")
0 and 1
0 and 2
1 and 2
Метод combinations_with_replacements делает то же самое, но допускает повторы.
from itertools import combinations_with_replacement
for x, y in combinations_with_replacement(range(3), 2):
print(f"{x} and {y}")
0 and 0
0 and 1
0 and 2
1 and 1
1 and 2
2 and 2