Как и зачем использовать Python генераторы

| Python

Генераторы стали важной частью Python с тех пор, как были представлены в PEP 255. Функция генератор объявлять функцию, которая ведет себя как итератор. Она позволяет программистам создавать быстрым, простым и чистым способом итератор.

Что такое итератор? Итератор - это объект перечислитель, который можно итерировать в цикле. Он используется для абстрагирования контейнера данных, чтобы заставить его вести себя как итерируемый объект. Самые часто используемые итерируемые объекты: строки, списки, картежи и словари. Итератор определяется классом, который реализует протокол Iterator. Протокол просматривает два метода внутри класса: iter и next. Когда необходимо сделать итераторы?

Экономия памяти

Итераторы не вычисляют значение каждого элемента при создании объекта. Они вычисляют значение элемента, только когда происходит обращение к нему. Это называется ленивыми вычислениями. Ленивые вычисления полезны, когда нужны большие наборы данных для вычисления. Они позволяют сразу начать использовать данные, раньше, чем сгенерируется весь набор данных.

Предположим, нужно получить все простые числа, которые меньше максимального.Сначала мы определяем функцию, которая проверяет, является ли число простым:

def check_prime(number):
    for divisor in range(2, int(number ** 0.5) + 1):
        if number % divisor == 0:
            return False
    return True

Затем определим класс итератора, который будет включать методы iter и next:

class Primes:
    def __init__(self, max):
        self.max = max
        self.number = 1
    def __iter__(self):
        return self
    def __next__(self):
        self.number += 1
        if self.number >= self.max:
            raise StopIteration
        elif check_prime(self.number):
            return self.number
        else:
            return self.__next__()

Объект Primes создаётся с максимальным значением. Если следующее простое число больше max, то итератор будет вызывать исключение StopIteration, которое завершает итератор. Когда запрашивается следующий элемент в итераторе, он будет увеличивать число на 1 и проверка, является ли оно простым числом. Если это не так, она снова вызовет next, пока номер не станет простым. После этого итератор возвращает число. Используя итератор, мы не создаем список простых чисел в нашей памяти. Вместо этого генерируется следующее простое число каждый раз, когда оно запрашивается.

Протестируем:

primes = Primes(100000000000)
print(primes)
for x in primes:
    print(x)
---------
<__main__.Primes object at 0x1021834a8>
2
3
5
7
11
…

Каждая итерация объекта Primes вызывает next для генерации следующего простого числа. Итераторы могут повторяться только один раз. Если попытаетесь повторно выполнить итерации по объекту primes, то значение возвращено не будет. Он будет вести себя как пустой список. Теперь, когда объяснено, что такое итераторы и как их создавать, перейдем к генераторам.

Генераторы

Напомним, что функции генератора позволяют нам создавать итераторы более простым способом. Генераторы вводят инструкцию yield в Python. Она работает подобно return, потому что возвращает значение. Разница в том, что сохраняется состояние функции. В следующий раз, когда вызывается функция, выполнение продолжается с того места, где оно было остановлено, с теми же значениями переменных, что и перед yield. Если преобразовать итератор Primes в генератор, он будет выглядеть так:

def Primes(max):
    number = 1
    while number < max:
        number += 1
        if check_prime(number):
            yield number
primes = Primes(100000000000)
print(primes)
for x in primes:
    print(x)
---------
<generator object Primes at 0x10214de08>
2
3
5
7
11
…

Теперь код довольно питоничен! Также можно использовать генератор выражения, введенный в PEP 289. Он эквивалентен генератору списка. Он ведёт себя как список, но выражение окружено в (), а не []. Следующий код заменяет нашу функцию генератор выше:

primes = (i for i in range(2, 100000000000) if check_prime(i))
print(primes)
for x in primes:
    print(x)
---------
<generator object <genexpr> at 0x101868e08>
2
3
5
7
11
…

Резюме

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