Страницы

Поиск по вопросам

среда, 28 ноября 2018 г.

Вопрос по x = yield f() — какой порядок вызова и присваивания?

объясните, что означает эта строка внутри функции
some_variable = yield some_function()
если я правильно понимаю, то yield some_function() в данном случае отправляет результат в генератор, после чего записывается в переменную и продолжается выполнение кода после этого присваивания.
Если я понял верно, тогда вопрос. А присваивание происходит после того, как происходит возврат и обработка текущего результата, или сначала выполняется до конца текущая функция, затем в генератор возвращается результат?


Ответ

Если создать генератор g = gen(), то сам он ничего не делает, пока снаружи его не продвинут с помощью next(g). next(g) равнозначен g.send(None)
g.send(y) позволяет передать в генератор значение:
def gen(): while True: x = yield '.' print(x * x)
Так как g = gen() само по себе не продвигает генератор, то необходимо вызывать next(g), чтобы код дошёл до строчки с x = yield '.'. Так как в начале функции нет yield, то первый вызов всегда эквивалентен g.send(None). Значения y, переданные в последующих вызовах, уже присваиваются x
>>> g = gen() >>> z = g.send(None) >>> z '.' >>> z = g.send(1) 1 >>> z '.' >>> z = g.send(2) 4 >>> z '.'
В данном случае генератор генерирует '.' постоянно.
Видно, что первый вызов g.send(None) не дошёл до x (иначе в дальнейшем получили бы TypeError из-за None * None). То есть значение переданное в g.send() возвращается из yield, на котором генератор стоял, а затем код продвигается до следующего yield и его аргумент возвращается как результат и присваивается z
Теперь можно объяснить, что происходит с:
def gen(): some_variable = yield some_function()
по шагам:
Создаём геренратор:
>>> g = gen()
Код внутри генератора при этом не выполняется Продвигаем код до первого yield, вызывается some_function(), получаем значение some_function()
>>> g.send(None) 'result from some_function()'
Имя some_variable ещё не присвоено по окончанию g.send() Следующий вызов g.send(1) присваивает 1 к some_variable имени и выбрасывает StopIteration, когда код покидает генератор:
>>> g.send(1) Traceback (most recent call last): File "", line 1, in StopIteration

Подобные генераторы или как их иногда называют сопрограммы (coroutine) могут использоваться, чтобы перемежать исполнение нескольких функций в одном потоке. Практический пример, можно выполнить NREQUESTS сетевых запросов по NCONCURRENT одновременных запросов, используя twisted.internet.defer.inlineCallbacks декоратор:
#!/usr/bin/env python """Make NREQUESTS http POST requests `NCONCURRENT` at the time using treq module.""" import os from twisted.internet import defer, task # $ pip install twisted import treq # $ pip install treq
def n_at_a_time(it, n): """Iterate over `it` concurently `n` items at a time.
`it` - an iterator creating Deferreds `n` - number of concurrent iterations return a deferred that fires on completion """ return defer.DeferredList([task.coiterate(it) for _ in range(n)])
@defer.inlineCallbacks def make_request(): # post request response = yield treq.post('http://localhost:8001/post', {'a':'b', 'c': 'd'}) data = yield response.content() # read response
def main(reactor): reqs = (make_request() for _ in range(int(os.environ['NREQUESTS']))) return n_at_a_time(reqs, n=int(os.environ['NCONCURRENT']))
task.react(main)
По похожему, но несколько другому механизму (основанному на yield from, а не yield) работают asyncio coroutines.

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

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