Генераторы
В этой статье я хочу рассказать о такой интересной особенности Python, которой нет во многих других языках, и которая носит название "генераторы". Что, если я вам скажу, что вот такой код, при некоторых условиях, может выполняться до бесконечности?
for number in fibonacci_numbers: print(number)
Наверное у вас появится закономерный вопрос: "Как же так, ведь fibonacci_numbers должен быть ограничен по длине и когда-нибудь цикл закончится?". И вы были бы правы, будь эта переменная списком или чем-то подобным, но в примере выше, она - генератор и не имеет конца. Заинтересовались? Тогда давайте разберёмся, как работает эта сущность в Python.
Для начала, поймём, как работает цикл for. На самом деле, всё, что он делает:
- Вызывает на нашем объекте "магический" метод __iter__ и получает специальный объект-итератор, у которого есть метод __next__
- При каждой итерации, этот метод вызывается и значение переменной, указанной нами в цикле обновляется.
- Итерация прекращается, когда метод __next__ вызывает исключение StopIteration.
То есть, мы можем сделать генератор своими руками. Прикрепляю ссылку на Pastebin потому что там есть подсветка синтаксиса и копировать код оттуда проще.
class FibonacciGenerator: """ Класс, объект которого мы будем создавать """ class FibonacciIterator: """ Вспомогательный класс, который реализует метод __next__ Этот метод вызывается на каждой итерации for """ def __init__(self): """ Начинаем нашу последовательность с двух единиц """ self.first_number = 1 self.second_number = 1 self.iterations = 0 def __next__(self): self.iterations += 1 if self.iterations <= 2: # Первые 2 раза мы возвращаем единицу return 1 self.first_number, self.second_number = self.second_number, self.first_number + self.second_number return self.second_number def __iter__(self): return self.FibonacciIterator() fib_gen = FibonacciGenerator() for i in fib_gen: print(i)
Если вы запустите у себя этот код, то увидите бесконечно выводящиеся числа Фибоначи. Теперь о том, как это работает: for, когда начинает свою работу, вызывает метод __iter__ и получает объект класса FibonacciIterator. На нём, перед каждой итерацией, он запускает метод __next__ и получает следующее число, которое записывается в переменную i. Согласитесь, выглядит не очень красиво.Наверное поэтому в Python существует специальный синтаксис, призванный упростить создание генераторов. Главным средством для этого, является ключевое слово yield. Это как return, но функция не заканчивается, а её состояние запоминается и при вызове __next__ продолжается с того же места. Пример с числами Фибоначи:
def fibonacci_generator(): """ Эта функция возвращает специальный объект-генератор """ yield 1 yield 1 first, second = 1, 1 while True: first, second = second, first + second yield second test_generator = fibonacci_generator() for i in test_generator: print(i)
Эта программа делает всё то же самое. test_generator - объект специального класса generator, который реализует то, что мы описали. Кроме yield есть ещё конструкция yield from <collection>, которая делает yield последовательно каждого элемента <collection>. Выглядит это так:
def yield_from_generator(): yield from (1,2,3)
Объект, созданный этой функцией вернёт 1, потом 2, потом 3.
Бонус:
У генераторов есть сокращённый синтаксис, как в list comprehensions, но вместо квадратных скобок надо поставить круглые. Например вот генератор квадратов чисел от 1 до 100:
(i**2 for i in range(1, 101))
Генераторы - очень мощный инструмент, который может, при правильном применении, сильно упростить код. Используйте с умом и наслаждайтесь красотой и мощью Python!
До новых встреч в группе SnakeBlog