Итераторы

Источники:

  • Intermediate Python
  • Лутц, главы 14, 20, 29. (TODO: в следующий проход интегрировать сюда главы 20 и 29, кроме того, сейчас тут немного дублируется информация при переходе от итераторов к генераторам).

Для начала нам стоит познакомиться с итераторами. Как подсказывает Wiki, итератор — это интерфейс, предоставляющий доступ к элементам коллекции (массива или контейнера). Здесь важно отметить, что итератор только предоставляет доступ, но не выполняет итерацию по ним. Это может звучать довольно запутано, так что остановимся чуть подробнее. Тему итераторов можно разбить на три части:

  • Итерируемый объект
  • Итератор
  • Итерация

Итерируемым объектом в Python называется любой объект, имеющий методы __iter__ или __getitem__, которые возвращают итераторы или могут принимать индексы. В итоге итерируемый объект это объект, который может предоставить нам итератор.

Итератором в Python называется объект, который имеет метод next (Python 2) или __next__. Вот и все. Это итератор.

Итерация - это процесс получения элементов из какого-нибудь источника, например списка. Итерация - это процесс перебора элементов объекта в цикле.

Теперь, когда у нас есть общее понимание основных принципов, перейдём к генераторам.

Генераторы

Генераторы это итераторы, по которым можно итерировать только один раз. Так происходит поскольку они не хранят все свои значения в памяти, а генерируют элементы "на лету". Генераторы можно использовать с циклом for или любой другой функцией или конструкцией, которые позволяют итерировать по объекту. В большинстве случаев генераторы создаются как функции. Тем не менее, они не возвращают значение также как функции (т.е. через return), в генераторах для этого используется ключевое слово yield.

Пример функции-генератора:

def generator_function():
    for i in range(10):
        yield i

for item in generator_function():
    print(item)

# Вывод: 0
# 1
# 2
# 3
# 4
# 5
# 6
# 7
# 8
# 9

Чуть более полезный пример вычисления чисел Фибоначчи:

# generator version
def fibon(n):
    a = b = 1
    for i in range(n):
        yield a
        a, b = b, a + b

# использование:
for x in fibon(1000000):
    print(x)

В случае списка все бы числа хранились в памяти:

def fibon(n):
    a = b = 1
    result = []
    for i in range(n):
        result.append(a)
        a, b = b, a + b
    return result

Функция next()

Встроенная функция next() позволяет переходить к следующему элементу коллеции.

>>> def generator_function():
...     for i in range(3):
...         yield i
...
>>> gen = generator_function()
>>> print(next(gen))
0
>>> print(next(gen))
1
>>> print(next(gen))
2
>>> print(next(gen))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

Когда по чему можно итерировать заканчивается, функция next() порождает исключение StopIteration

Цикл for автоматически перехватывает это исключение и перестает вызывать next().

Функция iter()

Попробуем проитерировать по строке с помощью next.

>>> str = 'Hello'
>>> next(str)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'str' object is not an iterator

Да, строка - итерируемый объект, но не итератор. Чтобы получить из строки итератор, используем встроенную функцию iter(). Она возвращает итератор из итерируемого объекта.

Не все является итерируемым объектом. Например, объект типа int не является итерируемым.

int_var = 1779
iter(int_var)
# Вывод: Traceback (most recent call last):
#          File "<stdin>", line 1, in <module>
#        TypeError: 'int' object is not iterable
# int не итерируемый объект

Добудем из строки итератор и пройдемся по нему функцией next.

>>> str = 'Hello'
>>> my_iter = iter(str)
>>> next(my_iter)
'H'

results matching ""

    No results matching ""