В этом уроке мы с вами разберем, что из себя представляют генераторы в программировании на языке Python. Кроме того, мы обсудим генераторные выражения и разницу между списками и генераторами, а также между функциями и генераторами.
Друзья, подписывайтесь на наш телеграм канал Pythonist. Там еще больше туториалов, задач и книг по Python.
Генератор это подвид итерируемых объектов, как список или кортеж. Он генерирует для нас последовательность значений, которую мы можем перебрать. Эту последовательность можно использовать для итерации в цикле for, но нельзя проиндексировать (т. е., перебрать ее можно только один раз). Давайте посмотрим, как создается такая последовательность значений при помощи генератора.
Для создания генератора в Python внутри функции вместо ключевого слова return используется ключевое слово yield. Обратите внимание на пример:
def counter(): i=1 while(i<=10): yield i i+=1
В этом примере мы определили генератор с именем counter() и назначили значение 1 локальной переменной i. Цикл while будет выполняться, пока i меньше или равно 10. Внутри цикла мы возвращаем (yield) значение i и увеличиваем его на единицу.
Затем мы используем этот генератор в цикле for.
for i in counter(): print(i)
Вывод:
1 2 3 4 5 6 7 8 9 10
Чтобы разобраться в том, как работает этот код, давайте начнем с цикла for. Этот цикл выводит каждый элемент генератора (т. е., каждый элемент, возвращаемый генератором).
Мы начинаем с i=1. Таким образом, первый элемент, возвращаемый генератором, это 1. Цикл for выводит этот элемент на экран благодаря ключевому слову print. Затем i инкрементируется до 2. Весь процесс повторяется, пока i не инкрементируется до 11 (т. е., пока условие в цикле while не даст false).
Но если вы забудете добавить инкремент i, вы получите бесконечный генератор. Дело в том, что генератору в каждый момент времени нужно удерживать в памяти только одно значение. Таким образом, нет никаких ограничений памяти.
def even(x): while x%2==0: yield 'Even' for i in even(2): print(i)
Вывод:
Even Even Even Even Even Even Even Even Even Even Even Even Even
EvenTraceback (самый недавний вызов идет последним):
File “”, line 2, in print(i) KeyboardInterrupt
Поскольку 2 это четное число, 2%2 это всегда 0. Поэтому условие в цикле while всегда будет соблюдаться (всегда true). В результате генератор even() продолжает возвращать значение Even, пока мы не прервем выполнение цикла вручную (сочетанием клавиш Ctrl+C).
Обратите внимание, что генератор может содержать больше одного ключевого слова yield. Примерно так же, как функция может иметь больше одного ключевого слова return.
def my_gen(x): while( x> 0): if x%2==0: yield 'Even' else: yield 'Odd' x-=1 for i in my_gen(7): print(i)
Вывод:
Odd Even Odd Even Odd Even Odd
Здесь все просто. Если вы примените функцию list() к вызову генератора, она вернет список возвращенных генератором значений, в том порядке, в котором они возвращались. В следующем примере генератор возвращает квадраты чисел, если эти квадраты четные.
def even_squares(x): for i in range(x): if i**2%2==0: yield i**2
Чтобы создать список из возвращаемых генератором значений, мы просто применяем функцию list() к вызову генератора. Мы не перебираем эти значения при помощи цикла for.
print(list(even_squares(10))) # Вывод: [0, 4, 16, 36, 64]
Как видите, для чисел в диапазоне 0-9 (не 10, потому что диапазон (10) это числа 0-9), четные квадраты это 0, 4, 16, 36 и 64. Остальные — 1, 9, 25, 49, 81 — нечетные. Поэтому они не возвращаются генератором.
Разница между ними очень проста. Список сразу удерживает в памяти определенное число значений. А генератор в каждый отдельный момент удерживает только одно значение — то, которое он возвращает. Вот почему генераторы требуют куда меньше памяти. Когда мы применяем генератор, нам также не приходится ждать рендеринга всех значений.
Чтобы разобраться в различиях между генераторами и функциями, давайте сначала разберем разницу между ключевыми словами return и yield.
Когда интерпретатор доходит до ключевого слова return, выполнение функции полностью прекращается. Но когда он доходит до ключевого слова yield, программа приостанавливает выполнение функции и возвращает значение в итерируемый объект. После этого интерпретатор возвращается к генератору, чтобы повторить процесс для нового значения.
Кроме того, при прекращении выполнения функции ее локальные переменные стираются. В генераторах ситуация другая. Взгляните:
def mygen(): i=7 while i>0: yield i i-=1 for i in mygen(): print(i)
Вывод:
7 6 5 4 3 2 1
Для создания генераторов на скорую руку можно использовать выражения (как и для генераторов списка). Давайте возьмем для этого список:
>>> mylist = [1, 3, 6, 10] >>> (x**2 for x in mylist) <generator object <genexpr> at 0x7f7a06896af0>
Как видим, мы получили генератор. Но чтобы получить доступ к значениям, нужно сохранить его в переменной, а затем применить к этой переменной функцию next().
>>> a = (x**2 for x in mylist) >>> next(a) 1 >>> next(a) 9 >>> next(a) 36 >>> next(a) 100
Traceback (самый недавний вызов идет последним):
File “”, line 1, in next(a) StopIteration
Вот и все, что мы хотели рассказать вам о генераторах в Python. Надеемся, вам понравилось наше объяснение.
Теперь, когда вы знаете о преимуществах генераторов по сравнению со списками и функциями, вы понимание их важность. Что-то мы можем делать при помощи генератора, что-то — при помощи функции или даже генератора списка. Но использование генераторов наиболее эффективно.
Управление памятью - важный, но часто упускаемый из виду аспект программирования. При неправильном подходе оно…
Как возникает круговой импорт? Эта ошибка импорта обычно возникает, когда два или более модуля, зависящих…
Вы когда-нибудь оказывались в ситуации, когда скрипт на Python выполняется очень долго и вы задаетесь…
В этом руководстве мы разберем все, что нужно знать о символах перехода на новую строку…
Блок if __name__ == "__main__" в Python позволяет определить код, который будет выполняться только при…
Давайте разберем, как настроить модульные тесты для экземпляров классов. Мы напишем тесты для проверки функциональности…