Поиск по блогу

вторник, 11 ноября 2014 г.

К вопросу об использовании yield вместо return

Для понимания того, как оуществляются отложенные операции. Классический пример - функция на каждой итерации цикла передает результат другой функции, а сама ждет следующего вызова... Здесь конспект из главы 20 Лутца.

На сегодняшний день язык Python более широко поддерживает отложенные операции, чем в прошлом, – он предоставляет инструменты, позволяющие реализовать воспроизведение результатов по требованию, а не всех сразу. В частности, существуют две языковые конструкции, откладывающие создание результатов, когда это возможно:

•• Функции-генераторы – выглядят как обычные инструкции def, но для возврата результатов по одному значению за раз используют инструкцию yield, которая приостанавливает выполнение функции.

•• Выражения-генераторы – напоминают генераторы списков, о которых рассказывалось в предыдущем разделе, но они не конструируют список с результатами, а возвращают объект, который будет воспроизводить результаты по требованию.

В отличие от обычных функций, которые возвращают значение и завершают работу, функции-генераторы автоматически приостанавливают и возобновляют свое выполнение, при этом сохраняя информацию, необходимую для генерации значений.

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

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

функция-генератор возобновляет работу, ее выполнение продолжается с первой инструкции, следующей за инструкцией yield.

Это позволяет функциям воспроизводить последовательности значений в течение долгого времени, вместо того чтобы создавать всю последовательность сразу и возвращать ее в виде некоторой конструкции, такой как список.

In [12]:
def gensquares(n):
    for i in range(n):
        yield i**2 # Позднее продолжить работу с этого места

Эта функция поставляет значение и тем самым возвращает управление вызывающей программе на каждой итерации цикла – когда она возобновляет работу, восстанавливается ее предыдущее состояние и управление передается непосредственно в точку, находящуюся сразу же за инструкцией yield.

Например, при использовании в заголовке цикла for управление возвращается функции на каждой итерации в точку, находящуюся сразу же за инструкцией yield:

In [13]:
for i in gensquares(5): # Возобновить работу функции
    print i, ":" # Вывести последнее полученное значение
0 :
1 :
4 :
9 :
16 :

In [14]:
x = gensquares(4)
x
Out[14]:
<generator object gensquares at 0x00000000027350D8>

Здесь функция вернула объект-генератор, который поддерживает протокол итераций, с которым мы познакомились в главе 14, – то есть имеет метод next, который запускает функцию или возобновляет ее работу с места, откуда было поставлено последнее значение, а также возбуждает исключение StopIteration по достижении конца последовательности значений. Для удобства в языке Python была создана встроенная функция next, вызов next(X) которой производит вызов метода X.next() объекта:

In []:
>>> next(x) # То же, что и x.__next__() в версии 3.0
0
>>> next(x) # В версии 2.6 используйте вызов x.next() или next()
1
>>> next(x)
4
>>> next(x)
9
>>> next(x)
In [15]:
next(x)
Out[15]:
0
In [16]:
next(x)
Out[16]:
1

Как мы узнали в главе 14, циклы for (и другие итерационные контексты) работают с генераторами точно так же – вызывают метод next в цикле, пока не будет получено исключение. Если итерируемый объект не поддерживает этот протокол, вместо него цикл for использует протокол доступа к элементам по индексам.

Обратите внимание, что в этом примере мы могли бы просто сразу создать список всех значений:

In [18]:
>>> def buildsquares(n):
    res = []
    for i in range(n): res.append(i**2)
    return res

>>> for x in buildsquares(5): print(x, ' : ')
(0, ' : ')
(1, ' : ')
(4, ' : ')
(9, ' : ')
(16, ' : ')

В такой ситуации мы могли бы использовать любой из приемов: цикл for, функцию map или генератор списков:

In [22]:
for x in [n**2 for n in range(5)]:
    print(x,' : ')
(0, ' : ')
(1, ' : ')
(4, ' : ')
(9, ' : ')
(16, ' : ')

In [24]:
for x in map((lambda x:x**2), range(5)):
    print(x, ' : ')
(0, ' : ')
(1, ' : ')
(4, ' : ')
(9, ' : ')
(16, ' : ')

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



Посты чуть ниже также могут вас заинтересовать

Комментариев нет:

Отправить комментарий