Подтвердить что ты не робот

Генератор функции "отправить" генератора питона?

Может ли кто-нибудь дать мне пример того, почему существует функция "send", связанная с функцией генератора Python? Я полностью понимаю функцию выхода. Однако функция отправки меня сбивает с толку. Документация по этому методу запутана:

generator.send(value)

Возобновляет выполнение и "отправляет" значение в функцию генератора. Аргумент value становится результатом текущего выражения yield. Метод send() возвращает следующее значение, генерируемое генератором, или вызывает StopIteration, если генератор выходит, не давая другого значения.

Что это значит? Я думал, что значение - это вход в функцию? Фраза "Метод send() возвращает следующее значение, полученное генератором", похоже, также является точной целью функции yield; yield возвращает следующее значение, полученное генератором...

Может ли кто-нибудь дать мне пример генератора, использующего send, который выполняет что-то, что не может?

4b9b3361

Ответ 1

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

>>> def double_inputs():
...     while True:
...         x = yield
...         yield x * 2
...
>>> gen = double_inputs()
>>> next(gen)       # run up to the first yield
>>> gen.send(10)    # goes into 'x' variable
20
>>> next(gen)       # run up to the next yield
>>> gen.send(6)     # goes into 'x' again
12
>>> next(gen)       # run up to the next yield
>>> gen.send(94.3)  # goes into 'x' again
188.5999999999999

Вы не можете сделать это только с yield.

Что касается того, почему это полезно, один из лучших вариантов использования, которые я видел, это Twisted @defer.inlineCallbacks. По сути, это позволяет вам написать такую функцию:

@defer.inlineCallbacks
def doStuff():
    result = yield takesTwoSeconds()
    nextResult = yield takesTenSeconds(result * 10)
    defer.returnValue(nextResult / 10)

То, что происходит, - то, что takesTwoSeconds() возвращает Deferred, которое является значением, обещающим, что значение будет вычислено позже. Twisted может запустить вычисления в другом потоке. Когда вычисление выполнено, оно передает его в отложенное состояние, а затем значение отправляется обратно в doStuff(). Таким образом, doStuff() может в конечном итоге выглядеть более или менее как обычная процедурная функция, за исключением того, что он может выполнять всевозможные вычисления и обратные вызовы и т.д. Альтернативой этой функции было бы сделать что-то вроде

def doStuff():
    returnDeferred = defer.Deferred()
    def gotNextResult(nextResult):
        returnDeferred.callback(nextResult / 10)
    def gotResult(result):
        takesTenSeconds(result * 10).addCallback(gotNextResult)
    takesTwoSeconds().addCallback(gotResult)
    return returnDeferred

Это намного более запутанный и громоздкий.

Ответ 2

Эта функция предназначена для написания сопрограммы

def coroutine():
    for i in range(1, 10):
        print("From generator {}".format((yield i)))
c = coroutine()
c.send(None)
try:
    while True:
        print("From user {}".format(c.send(1)))
except StopIteration: pass

печатает

From generator 1
From user 2
From generator 1
From user 3
From generator 1
From user 4
...

Посмотрите, как управление передается вперед и назад? Это сопрограммы. Они могут использоваться для всех видов прохладных вещей, таких как asynch IO и т.д.

Подумайте об этом так, с генератором и без отправки, это односторонняя улица

==========       yield      ========
Generator |   ------------> | User |
==========                  ========

Но с отправкой это становится двухсторонней улицей.

==========       yield       ========
Generator |   ------------>  | User |
==========    <------------  ========
                  send

Что открывает дверь пользователю, настраивающему поведение генераторов "на лету", а генератор отвечает на пользователя.

Ответ 3

Это может помочь кому-то. Вот генератор, на который не влияет функция отправки. Он принимает параметр number при создании экземпляра и не подвергается влиянию send:

>>> def double_number(number):
...     while True:
...         number *=2 
...         yield number
... 
>>> c = double_number(4)
>>> c.send(None)
8
>>> c.next()
16
>>> c.next()
32
>>> c.send(8)
64
>>> c.send(8)
128
>>> c.send(8)
256

Теперь вот как вы будете делать один и тот же тип функции с помощью send, поэтому на каждой итерации вы можете изменить значение числа:

def double_number(number):
    while True:
        number *= 2
        number = yield number

Вот как это выглядит, так как вы можете видеть, что посылка нового значения для числа изменяет результат:

>>> def double_number(number):
...     while True:
...         number *= 2
...         number = yield number
...
>>> c = double_number(4)
>>> 
>>> c.send(None)
8
>>> c.send(5) #10
10
>>> c.send(1500) #3000
3000
>>> c.send(3) #6
6

Вы также можете поместить это в цикл for как таковой:

for x in range(10):
    n = c.send(n)
    print n

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

Ответ 4

Метод send реализует сопрограммы.

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

Ответ 5

Некоторые примеры использования генератора и send()

Генераторы с send() позволяют:

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

Вот несколько вариантов использования:

Наблюдаемая попытка следовать рецепту

У нас есть рецепт, который ожидает предопределенный набор входов в некотором порядке.

Мы можем:

  • создать экземпляр watched_attempt из рецепта
  • пусть он получает некоторые входы
  • с каждым вводом возвращаем информацию о том, что сейчас находится в банке
  • с каждой проверкой ввода, что вход является ожидаемым (и не сработает, если это не так)

    def recipe():
        pot = []
        action = yield pot
        assert action == ("add", "water")
        pot.append(action[1])
    
        action = yield pot
        assert action == ("add", "salt")
        pot.append(action[1])
    
        action = yield pot
        assert action == ("boil", "water")
    
        action = yield pot
        assert action == ("add", "pasta")
        pot.append(action[1])
    
        action = yield pot
        assert action == ("decant", "water")
        pot.remove("water")
    
        action = yield pot
        assert action == ("serve")
        pot = []
        yield pot
    

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

>>> watched_attempt = recipe()                                                                         
>>> watched_attempt.next()                                                                                     
[]                                                                                                     

Вызов .next() необходим для запуска выполнения генератора.

Возвращаемое значение показывает, что наш банк в настоящее время пуст.

Теперь выполните несколько действий, следующих за тем, что ожидает рецепт:

>>> watched_attempt.send(("add", "water"))                                                                     
['water']                                                                                              
>>> watched_attempt.send(("add", "salt"))                                                                      
['water', 'salt']                                                                                      
>>> watched_attempt.send(("boil", "water"))                                                                    
['water', 'salt']                                                                                      
>>> watched_attempt.send(("add", "pasta"))                                                                     
['water', 'salt', 'pasta']                                                                             
>>> watched_attempt.send(("decant", "water"))                                                                  
['salt', 'pasta']                                                                                      
>>> watched_attempt.send(("serve"))                                                                            
[] 

Как мы видим, банк окончательно пуст.

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

>>> watched_attempt = running.recipe()                                                                         
>>> watched_attempt.next()                                                                                     
[]                                                                                                     
>>> watched_attempt.send(("add", "water"))                                                                     
['water']                                                                                              
>>> watched_attempt.send(("add", "pasta")) 

---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
<ipython-input-21-facdf014fe8e> in <module>()
----> 1 watched_attempt.send(("add", "pasta"))

/home/javl/sandbox/stack/send/running.py in recipe()
     29
     30     action = yield pot
---> 31     assert action == ("add", "salt")
     32     pot.append(action[1])
     33

AssertionError:

Обратите внимание, что:

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

Текущие итоги

Мы можем использовать генератор для отслеживания общего количества отправленных на него значений.

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

from collections import namedtuple

RunningTotal = namedtuple("RunningTotal", ["n", "total"])


def runningtotals(n=0, total=0):
    while True:
        delta = yield RunningTotal(n, total)
        if delta:
            n += 1
            total += delta


if __name__ == "__main__":
    nums = [9, 8, None, 3, 4, 2, 1]

    bookeeper = runningtotals()
    print bookeeper.next()
    for num in nums:
        print num, bookeeper.send(num)

Результат будет выглядеть так:

RunningTotal(n=0, total=0)
9 RunningTotal(n=1, total=9)
8 RunningTotal(n=2, total=17)
None RunningTotal(n=2, total=17)
3 RunningTotal(n=3, total=20)
4 RunningTotal(n=4, total=24)
2 RunningTotal(n=5, total=26)
1 RunningTotal(n=6, total=27)

Ответ 6

Метод send() контролирует, какое значение будет слева от выражения yield.

Чтобы понять, как yield отличается и какое значение он имеет, давайте сначала быстро обновим код заказа Python.

Раздел 6.15 Порядок оценки

Python оценивает выражения слева направо. Обратите внимание, что при оценке присваивания правая сторона оценивается перед левой стороной.

Таким образом, выражение a = b в правой части вычисляется первым.

Поскольку следующее демонстрирует, что a[p('left')] = p('right') правая часть вычисляется первой.

>>> def p(side):
...     print(side)
...     return 0
... 
>>> a[p('left')] = p('right')
right
left
>>> 
>>> 
>>> [p('left'), p('right')]
left
right
[0, 0]

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

Где именно приостановлено исполнение? Возможно, вы уже догадались... выполнение приостановлено между правой и левой сторонами выражения yield. Таким образом, new_val = yield old_val выполнение останавливается со знаком =, и значение справа (которое перед приостановкой, а также значение, возвращаемое вызывающей стороне) может отличаться от значения слева (которое значение присваивается после возобновления исполнения).

yield дает 2 значения, одно справа и другое слева.

Как вы контролируете значение в левой части выражения yield? с помощью .send().

6.2.9. Выражения доходности

Значение выражения yield после возобновления зависит от метода, который возобновил выполнение. Если используется __next__() (обычно через встроенную функцию for или next()), то результатом будет None. В противном случае, если используется send(), результатом будет значение, переданное этому методу.

Ответ 7

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

def echo_sound():

    thing_to_say = '<Sound of wind on cliffs>'
    while True:
        thing_to_say = (yield thing_to_say)
        thing_to_say = '...'.join([thing_to_say]+[thing_to_say[-6:]]*2)
        yield None  # This is the return value of send.

gen = echo_sound()

print 'You are lost in the wilderness, calling for help.'

print '------'
in_message = gen.next()
print 'You hear: "{}"'.format(in_message)
out_message = 'Hello!'
print 'You yell "{}"'.format(out_message)
gen.send(out_message)

print '------'
in_message = gen.next()
print 'You hear: "{}"'.format(in_message)
out_message = 'Is anybody out there?'
print 'You yell "{}"'.format(out_message)
gen.send(out_message)

print '------'
in_message = gen.next()
print 'You hear: "{}"'.format(in_message)
out_message = 'Help!'
print 'You yell "{}"'.format(out_message)
gen.send(out_message)

Вывод:

You are lost in the wilderness, calling for help.
------
You hear: "<Sound of wind on cliffs>"
You yell "Hello!"
------
You hear: "Hello!...Hello!...Hello!"
You yell "Is anybody out there?"
------
You hear: "Is anybody out there?...there?...there?"
You yell "Help!"