Maintaining State в генераторах Python

Содержание
Введение
Пример
Производительность
Генератор чисел Фибоначчи
Generator Expressions
Похожие статьи

Введение

В этой статье продолжается обзор генераторов в Python 3 начатый здесь

Создайте файл generators_demo.py и копируйте туда код из примеров.

Запустить файл можно командой

python3 generators_demo.py

Пример

Рассмотрим код, который будет возвращать из списка определённое количество неповторяющихся элементов

def take(count, iterable): counter = 0 for item in iterable: if counter == count: return counter += 1 yield item def distinct(iterable): seen = set() for item in iterable: if item in seen: continue yield item seen.add(item) # continue - finish current loop iteration and begin the next iteration immediately def run_pipeline(): items = [3, 6, 6, 2, 1, 1] for item in take(3, distinct(items)): print(item) run_pipeline()

distinct() - это генератор, который выдаёт по одному элементу, если этого элемента нет во множестве (в set) seen

take() - это тоже генератор - он просто берет определённое количество элементов

python generators_demo.py

3 6 2

Каждый вызов функции-генератора создаёт новый генератор-объект (generator object)

Чтобы разобраться в работе этого примера можно использовать debugger, например PyCharm

В качестве дополнительной иллюстрации, которая особенно пригодится чуть позже используем обычный print().

Добавим ещё пару элементов в items

def take(count, iterable): counter = 0 for item in iterable: print("take item", item) if counter == count: print("No need to take it, return now") return counter += 1 yield item def distinct(iterable): seen = set() for item in iterable: print("distinct item candidate", item) if item in seen: continue else: print("distinct item", item) yield item seen.add(item) # continue - finish current loop iteration and begin the next iteration immediately def run_pipeline(): items = [3, 6, 6, 2, 1, 1, 5, 5] for item in take(3, distinct(items)): print("run_pipeline item", item) print(item) run_pipeline()

distinct item candidate 3 distinct item 3 take item 3 run_pipeline item 3 3 distinct item candidate 6 distinct item 6 take item 6 run_pipeline item 6 6 distinct item candidate 6 distinct item candidate 2 distinct item 2 take item 2 run_pipeline item 2 2 distinct item candidate 1 distinct item 1 take item 1 No need to take it, return now Process finished with exit code 0

Как видите, сперва работает take(), затем distinct(), затем run_pipeline()

take() когда берёт 1 понимает, что она уже лишняя (нужно всего 3 элемента) и до 5 дело вообще не доходит.

Произведено ровно столько работы, сколько необходимо, это так называемое ленивое вычисление (lazy computing)

Если становится слишком сложно, можно уйти от этой схемы, заставив distinct() выполнить все вычисления прежде чем они попадут в take().

Для этого вызовем disctinct() из list()

def run_pipeline(): items = [3, 6, 6, 2, 1, 1] for item in take(3, list(distinct(items))): print("run_pipeline item", item)

distinct item candidate 3 distinct item 3 distinct item candidate 6 distinct item 6 distinct item candidate 6 distinct item candidate 2 distinct item 2 distinct item candidate 1 distinct item 1 distinct item candidate 1 distinct item candidate 5 distinct item 5 distinct item candidate 5 take item 3 run_pipeline item 3 take item 6 run_pipeline item 6 take item 2 run_pipeline item 2 take item 1 No need to take it, return now Process finished with exit code 0

В этом случае проделана лишняя работы - distinct() прошёлся по всем элементам и в результате получился список [3, 6, 2, 1, 5] из которого take() взял нужные три элемента.

В данном примере разница невелика, но если бы в items было не 8 а миллион элементов, мы бы её почувстовали

Производительность

Сравним скорость выполнения этих скриптов. Сделаем это с помощью декоратора prof_timer , который нужно поместить выше вызова run_pipeline()

def prof_timer(orig_func): import time def wrapper(*args, **kwargs): t1 = time.time() result = orig_func(*args, **kwargs) t2 = time.time() - t1 print(f'{orig_func.__name__} ran in: {t2} sec') return result return wrapper

@prof_timer def run_pipeline(): items = [3, 6, 6, 2, 1, 1, 5, 5]

Lazy:

run_pipeline ran in: 6.0249837585029e-05 sec

С list():

run_pipeline ran in: 6.4849853515625e-05 sec

Разница незаметна. Добавим элементов в items

@prof_timer def run_pipeline(): items = [ 3, 6, 6, 2, 1, 1, 5, 5, 3, 6, 6, 2, 1, 1, 5, 5, 3, 6, 6, 2, 1, 1, 5, 5, 3, 6, 6, 2, 1, 1, 5, 5, 3, 6, 6, 2, 1, 1, 5, 5, 3, 6, 6, 2, 1, 1, 5, 5, 3, 6, 6, 2, 1, 1, 5, 5, 3, 6, 6, 2, 1, 1, 5, 5, 3, 6, 6, 2, 1, 1, 5, 5, 3, 6, 6, 2, 1, 1, 5, 5, 3, 6, 6, 2, 1, 1, 5, 5, 3, 6, 6, 2, 1, 1, 5, 5, 3, 6, 6, 2, 1, 1, 5, 5, 3, 6, 6, 2, 1, 1, 5, 5, 3, 6, 6, 2, 1, 1, 5, 5, 3, 6, 6, 2, 1, 1, 5, 5, 3, 6, 6, 2, 1, 1, 5, 5, 3, 6, 6, 2, 1, 1, 5, 5, 3, 6, 6, 2, 1, 1, 5, 5, 3, 6, 6, 2, 1, 1, 5, 5, 3, 6, 6, 2, 1, 1, 5, 5, 3, 6, 6, 2, 1, 1, 5, 5, 3, 6, 6, 2, 1, 1, 5, 5, 3, 6, 6, 2, 1, 1, 5, 5, 3, 6, 6, 2, 1, 1, 5, 5, 3, 6, 6, 2, 1, 1, 5, 5, 3, 6, 6, 2, 1, 1, 5, 5, 3, 6, 6, 2, 1, 1, 5, 5,]

Lazy:

run_pipeline ran in: 0.00011014938354492188 sec

С list():

run_pipeline ran in: 0.0005578994750976562 sec

Lazy быстрее в 5 раз

Генератор чисел Фибоначчи

Генератор, который выдаёт числа Фибоначчи.

Для примера запустим вариант, который покажет первые 20 чисел Фибоначчи.

Чтобы запустить бесконечную генерацию - раскомментируйте нижний блок кода

def fib_gen(): yield 0 a = 1 b = 1 while True: yield b a, b = b, a+b g = fib_gen() for i in range(20): print(next(g)) # Uncomment to generate infinit number # of Fibonacci numbers # import time # time.sleep(3) # for f in fib_gen(): # print("\nf: ",f)

python fibon.py

0 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765

Generator Expressions

Синтаксис

(expr(item) for item in iterable)

Чтобы передать генератор в функцию как аргумент дополнительные скобки не обязательны

func(expr(item) for item in iterable)

Рассмотрим пример

million_squares = (x*x for x in range(1, 1000001)) print(million_squares) print(list(million_squares)[-10:]) print(list(million_squares)[-10:]) # [] # To recreate a generator from a # generator expression, you must # execute the expression again

python gen_expr.py

<generator object <genexpr> at 0x7fdc7ada4580> [999982000081, 999984000064, 999986000049, 999988000036, 999990000025, 999992000016, 999994000009, 999996000004, 999998000001, 1000000000000] []

Второй вызов list() ничего не дал, так как генератор уже отработал до конца.

Вычислим сумму чисел передав генератор в sum()

print(sum(x*x for x in range(1, 1000001)))

333333833333500000

Вычислим сумму всех простых чисел меньших 1000

def is_prime(x): from math import sqrt if x < 2: return False for i in range(2, int(sqrt(x)) + 1): if x % i == 0: return False return True print(sum(x for x in range(1001) if is_prime(x)))

76127

Похожие статьи
Итерация
Функции
Лямбда функции
all()
map()
Python
if, elif, else
Циклы
Методы
Генераторы списков
Генераторы словарей
Генераторы множеств
*args **kwargs
enum

Поиск по сайту

Подпишитесь на Telegram канал @aofeed чтобы следить за выходом новых статей и обновлением старых

Перейти на канал

@aofeed

Задать вопрос в Телеграм-группе

@aofeedchat

Контакты и сотрудничество:
Рекомендую наш хостинг beget.ru
Пишите на info@urn.su если Вы:
1. Хотите написать статью для нашего сайта или перевести статью на свой родной язык.
2. Хотите разместить на сайте рекламу, подходящую по тематике.
3. Реклама на моём сайте имеет максимальный уровень цензуры. Если Вы увидели рекламный блок недопустимый для просмотра детьми школьного возраста, вызывающий шок или вводящий в заблуждение - пожалуйста свяжитесь с нами по электронной почте
4. Нашли на сайте ошибку, неточности, баг и т.д. ... .......
5. Статьи можно расшарить в соцсетях, нажав на иконку сети: