Может кто-нибудь привести пример того, почему существует функция «send», связанная с функцией генератора Python? Я полностью понимаю функцию доходности. Однако, функция отправки сбивает меня с толку. Документация по этому методу свернута:
generator.send(value)
Возобновляет выполнение и «отправляет» значение в функцию генератора. Аргумент value становится результатом текущего выражения yield. Метод send () возвращает следующее значение, полученное генератором, или вызывает StopItered, если генератор завершает работу без получения другого значения.
Что это значит? Я думал, что значение было входом в функцию? Фраза «метод send () возвращает следующее значение, полученное генератором», по-видимому, также является точной целью функции yield; yield возвращает следующее значение, полученное генератором ...
Может кто-нибудь дать мне пример генератора, использующего send, который выполняет то, что yield не может?
send()
вызывается для запуска генератора, он должен вызыватьсяNone
в качестве аргумента, потому что нет выражения yield, которое могло бы получить значение.», Цитируется в официальном документе и для которого приведена цитата в вопросе отсутствует.Ответы:
Он используется для отправки значений в генератор, который только что дал. Вот искусственный (бесполезный) пояснительный пример:
Вы не можете сделать это только с
yield
.Что касается того, почему это полезно, один из лучших вариантов использования, которые я видел, это Twisted
@defer.inlineCallbacks
. По сути, это позволяет вам написать такую функцию:То, что происходит, - это то, что
takesTwoSeconds()
возвращает значениеDeferred
, которое обещает, что значение будет вычислено позже. Twisted может запустить вычисления в другом потоке. Когда вычисление выполнено, оно передает его в отложенное состояние, а затем значение возвращается вdoStuff()
функцию. Таким образом, онdoStuff()
может выглядеть более или менее похожим на обычную процедурную функцию, за исключением того, что он может выполнять все виды вычислений, обратных вызовов и т. Д. Альтернативой этой функции было бы сделать что-то вроде:Это намного более запутанный и громоздкий.
источник
Эта функция для написания сопрограмм
печать
Видите, как контроль передается взад и вперед? Это сопрограммы. Они могут быть использованы для всех видов интересных вещей, таких как асинхронный ввод-вывод и тому подобное.
Думайте об этом так, с генератором и без отправки, это улица с односторонним движением
Но с отправкой это становится улицей с двусторонним движением
Это открывает двери для пользователя, настраивая поведение генераторов на лету, и генератор отвечает пользователю.
источник
send()
генератор еще не достиг ключевого словаyield
.Это может кому-то помочь. Вот генератор, который не зависит от функции отправки. Он принимает параметр number при создании экземпляра и не зависит от send:
Теперь вот как вы выполняете функцию того же типа, используя send, поэтому на каждой итерации вы можете изменять значение number:
Вот как это выглядит, поскольку вы можете видеть, что отправка нового значения для числа меняет результат:
Вы также можете поместить это в цикл for как таковой:
Для получения дополнительной помощи ознакомьтесь с этим прекрасным учебником .
источник
send
? Простоеlambda x: x * 2
делает то же самое в гораздо менее запутанном виде.Некоторые варианты использования генератора и
send()
Генераторы с
send()
разрешением:Вот несколько вариантов использования:
Наблюдаю за попыткой следовать рецепту
Давайте получим рецепт, который ожидает предопределенный набор входов в некотором порядке.
Мы можем:
watched_attempt
экземпляр по рецептупри каждом входном контроле проверяется, что вход является ожидаемым (и потерпит неудачу, если это не так)
Чтобы использовать его, сначала создайте
watched_attempt
экземпляр:Призыв к
.next()
необходимо запустить выполнение генератора.Возвращаемое значение показывает, что наш банк в настоящее время пуст.
Теперь выполните несколько действий, следуя тому, что ожидает рецепт:
Как видим, горшок, наконец, пуст.
В случае, если кто-то не будет следовать рецепту, он потерпит неудачу (что может быть желательным результатом наблюдаемой попытки что-то приготовить - просто учась, мы не уделяем достаточно внимания при получении инструкций.
Заметь:
Промежуточные итоги
Мы можем использовать генератор для отслеживания итогового значения отправленных ему значений.
Каждый раз, когда мы добавляем число, возвращается количество входов и общая сумма (действительна на момент, когда в него был отправлен предыдущий вход).
Вывод будет выглядеть так:
источник
В
send()
контролируете метода , что значение слева от выражения текучести будет.Чтобы понять, как yield отличается и какое значение он имеет, давайте сначала быстро обновим код заказа Python.
Раздел 6.15 Порядок оценки
Таким образом, выражение
a = b
в правой части вычисляется первым.Как показывает следующее , что
a[p('left')] = p('right')
правая рука оценивается первым.Что делает yield ?, yield, приостанавливает выполнение функции и возвращает вызывающей стороне и возобновляет выполнение с того же места, на котором остановился перед приостановкой.
Где именно приостановлено исполнение? Возможно, вы уже догадались ... выполнение приостановлено между правой и левой сторонами выражения yield. Таким
new_val = yield old_val
образом, выполнение останавливается на=
знаке, и значение справа (которое находится перед приостановкой, а также является значением, возвращаемым вызывающей стороне) может отличаться от значения слева (которое является значением, назначаемым после возобновления). исполнение).yield
дает 2 значения, одно справа и другое слева.Как вы контролируете значение в левой части выражения yield? через
.send()
метод.6.2.9. Выражения доходности
источник
В
send
методе реализует сопрограмму .Если вы не сталкивались с сопрограммами, их сложно обернуть, потому что они изменяют ход программы. Вы можете прочитать хороший учебник для более подробной информации.
источник
Слово «урожайность» имеет два значения: производить что-то (например, приносить кукурузу) и останавливать, чтобы позволить кому-то / чему-либо еще продолжать движение (например, автомобили, уступающие пешеходам). Оба определения применяются к
yield
ключевому слову Python ; что делает функции генератора особенными, так это то, что в отличие от обычных функций, значения могут быть «возвращены» вызывающей стороне, просто останавливая, а не завершая функцию генератора.Проще всего представить генератор как один конец двунаправленной трубы с «левым» концом и «правым» концом; этот канал является средой, по которой значения передаются между самим генератором и телом функции генератора. Каждый конец канала имеет две операции:
push
отправляет значение и блокирует, пока другой конец канала не извлекает значение и ничего не возвращает; иpull
, который блокируется до тех пор, пока другой конец канала не вытолкнет значение и не вернет переданное значение. Во время выполнения выполнение отскакивает назад и вперед между контекстами по обе стороны канала - каждая сторона выполняется до тех пор, пока не отправит значение другой стороне, после чего остановится, разрешит выполнение другой стороне и ожидает значения в Возврат, после чего другая сторона останавливается, и она возобновляется. Другими словами, каждый конец канала работает с момента получения значения до момента отправки значения.Канал является функционально-симметричным, но - по соглашению, который я определил в этом ответе - левый конец доступен только внутри тела функции генератора и доступен через
yield
ключевое слово, в то время как правый конец является генератором и доступен черезsend
функция генератора . Как особые интерфейсы к их соответствующим концам трубы,yield
иsend
выполняют двойную обязанность: каждый из них и толкает и тянет значения к / от их концов трубы,yield
толкая вправо и тянет влево, в то время какsend
делает противоположное. Эта двойная обязанность - суть путаницы вокруг семантики таких утверждений, какx = yield y
. Ломатьyield
иsend
вниз в два этапа явно тяни / толкай сделает их семантика гораздо более ясно:g
это генератор.g.send
толкает значение влево через правый конец трубы.g
пауз, позволяющих запустить тело функции генератора.g.send
тянется влевоyield
и принимается на левом конце трубы. Вx = yield y
,x
присваивается значение вытягивания.yield
будет достигнута следующая строка, содержащая .yield
толкает значение вправо через левый конец трубы, обратно доg.send
. Вx = yield y
,y
проталкивается вправо через трубу.g.send
возобновляет и извлекает значение и возвращает его пользователю.g.send
следующем вызове вернитесь к шагу 1.Несмотря на цикличность, эта процедура имеет начало: когда
g.send(None)
- то, чтоnext(g)
является сокращением - сначала вызывается (нельзя передавать что-либо, кромеNone
первогоsend
вызова). И это может иметь конец: когда больше нетyield
операторов, которые должны быть достигнуты в теле функции генератора.Вы понимаете, что делает
yield
заявление (или, точнее, генераторы) таким особенным? В отличие отreturn
ключевого слова « трусливый» ,yield
он может передавать значения своему вызывающему и получать значения от своего вызывающего без прерывания функции, в которой он живет! (Конечно, если вы хотите завершить функцию - или генератор - также удобно иметьreturn
ключевое слово.) Когда встречаетсяyield
оператор, функция генератора просто делает паузу, а затем возвращается обратно туда, где она оставлена выкл при отправке другого значения. Иsend
это просто интерфейс для связи с внутренней функцией генератора извне.Если мы действительно хотим сломать эту аналогию «push / pull / pipe» настолько далеко, насколько сможем, мы получим следующий псевдокод, который действительно возвращает домой, помимо шагов 1-5,
yield
иsend
является двумя сторонами одной и той же трубымонет:right_end.push(None) # the first half of g.send; sending None is what starts a generator
right_end.pause()
left_end.start()
initial_value = left_end.pull()
if initial_value is not None: raise TypeError("can't send non-None value to a just-started generator")
left_end.do_stuff()
left_end.push(y) # the first half of yield
left_end.pause()
right_end.resume()
value1 = right_end.pull() # the second half of g.send
right_end.do_stuff()
right_end.push(value2) # the first half of g.send (again, but with a different value)
right_end.pause()
left_end.resume()
x = left_end.pull() # the second half of yield
goto 6
Ключ преобразования является то , что мы имеем раскол
x = yield y
иvalue1 = g.send(value2)
каждую из двух утверждений:left_end.push(y)
иx = left_end.pull()
; иvalue1 = right_end.pull()
иright_end.push(value2)
. Есть два особых случаяyield
ключевого слова:x = yield
иyield y
. Это синтаксический сахар соответственно дляx = yield None
и_ = yield y # discarding value
.Конкретные детали относительно точного порядка, в котором значения отправляются через канал, см. Ниже.
Далее следует довольно длинная конкретная модель вышесказанного. Во- первых, следует в первую очередь отметить , что для любого генератора
g
,next(g)
точно эквивалентноg.send(None)
. Имея это в виду, мы можем сосредоточиться только на том, какsend
работает, и говорить только о продвижении генератора сsend
.Предположим, у нас есть
Теперь определение
f
грубо десугаров для следующей обычной (не генераторной) функции:В этой трансформации произошло следующее
f
:left_end
к которому будет обращаться вложенная функция иright_end
который будет возвращен и получен доступ из внешней области видимости -right_end
это то, что мы знаем как объект генератора.left_end.pull()
естьNone
потребляемое значение в процессе.x = yield y
заменен двумя строками:left_end.push(y)
иx = left_end.pull()
.send
функцию дляright_end
, которая является аналогом двух строк, которыми мы заменилиx = yield y
оператор на предыдущем шаге.В этом фэнтезийном мире, где функции могут продолжаться после возвращения,
g
назначаетсяright_end
и затемimpl()
вызывается. Итак, в нашем примере выше, если бы мы следили за выполнением строки за строкой, то примерно следующее:Это отображается точно в 16-шаговый псевдокод выше.
Существуют и другие детали, например, как распространяются ошибки и что происходит, когда вы достигаете конца генератора (канал закрыт), но это должно прояснить, как работает основной поток управления при
send
использовании.Используя те же правила десагеринга, давайте рассмотрим два особых случая:
По большей части они desugar так же, как
f
, единственные различия вyield
том, как трансформируются утверждения:Во-первых, переданное значение
f1
сначала отбрасывается (уступается), а затем все значения, которые извлекаются (отправляются), возвращаются (возвращаются) назад. Во втором,x
не имеет значения (пока), когда это впервые приходитpush
, так что возникаетUnboundLocalError
.источник
yield
?send
;send(None)
для перемещения курсора к первомуyield
оператору требуется один вызов , и только после этого последующиеsend
вызовы фактически отправляют «реальное» значениеyield
.f
будетyield
в какой-то момент, и, следовательно, ждать, пока она не получитsend
от вызывающей стороны? С нормальной функцией cal интерпретатор просто начнет выполнятьсяf
сразу, верно? В конце концов, в Python нет никакой компиляции AOT. Вы уверены, что это так? (не подвергая сомнению то, что вы говорите, я действительно просто озадачен тем, что вы здесь написали). Где я могу прочитать больше о том, как Python знает, что ему нужно подождать, прежде чем он начнет выполнять остальную часть функции?send(None)
дает соответствующее значение (например,1
) без отправкиNone
в генератор, предполагает, что первый вызовsend
- это особый случай. Это сложный интерфейс для дизайна; если вы позволите первомуsend
отправить произвольное значение, то порядок полученных значений и отправленных значений будет на единицу меньше по сравнению с тем, что есть в настоящее время.Это меня тоже смутило. Вот пример, который я сделал, пытаясь настроить генератор, который выдает и принимает сигналы в чередующемся порядке (yield, accept, yield, accept) ...
Выход:
источник