¿Cuál es el propósito de la función "enviar" en los generadores de Python?

Resuelto Tommy asked hace 11 años • 9 respuestas

Entiendo yield. sendPero, ¿qué hace la función de un generador ? La documentación dice:

generator.send(value)

Reanuda la ejecución y "envía" un valor a la función generadora. El valueargumento se convierte en el resultado de la yieldexpresión actual. El send()método devuelve el siguiente valor generado por el generador, o aumenta StopIterationsi el generador sale sin generar otro valor.

¿Qué significa eso? Pensé valueque era la entrada a la función del generador. La frase "El send()método devuelve el siguiente valor arrojado por el generador" parece ser también el propósito exacto de yield, que también devuelve el siguiente valor arrojado por el generador.

¿Existe un ejemplo de un generador que utiliza sendalgo que yieldno puede lograr?

Tommy avatar Oct 11 '13 00:10 Tommy
Aceptado

Se utiliza para enviar valores a un generador que acaba de rendir. Aquí hay un ejemplo explicativo artificial (no útil):

>>> 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

No puedes hacer esto solo con yield.

En cuanto a por qué es útil, uno de los mejores casos de uso que he visto es el de Twisted @defer.inlineCallbacks. Básicamente, te permite escribir una función como esta:

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

Lo que sucede es que takesTwoSeconds()devuelve a Deferred, que es un valor que promete que se calculará un valor más adelante. Twisted puede ejecutar el cálculo en otro hilo. Cuando finaliza el cálculo, lo pasa al diferido y luego el valor se envía de regreso a la doStuff()función. Por lo tanto, doStuff()puede terminar pareciéndose más o menos a una función de procedimiento normal, excepto que puede realizar todo tipo de cálculos y devoluciones de llamadas, etc. La alternativa antes de esta funcionalidad sería hacer algo como:

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

Es mucho más complicado y difícil de manejar.

Claudiu avatar Oct 10 '2013 17:10 Claudiu

Esta función es para escribir corrutinas.

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

huellas dactilares

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

¿Ves cómo el control se pasa de un lado a otro? Esas son corrutinas. Se pueden usar para todo tipo de cosas interesantes como Asynch IO y similares.

Piénselo así, con un generador y sin envío, es una calle de sentido único

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

Pero con enviar, se convierte en una vía de doble sentido.

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

Lo que abre la puerta para que el usuario personalice el comportamiento de los generadores sobre la marcha y el generador responda al usuario.

daniel gratzer avatar Oct 10 '2013 17:10 daniel gratzer

Esto puede ayudar a alguien. Aquí hay un generador que no se ve afectado por la función de envío. Toma el parámetro numérico al crear instancias y no se ve afectado por el envío:

>>> 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

Ahora así es como harías el mismo tipo de función usando enviar, de modo que en cada iteración puedas cambiar el valor del número:

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

Así es como se ve, como puede ver, enviar un nuevo valor para el número cambia el resultado:

>>> 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

También puedes poner esto en un bucle for como tal:

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

Para obtener más ayuda, consulte este fantástico tutorial .

radtek avatar Oct 06 '2014 13:10 radtek

El send()método controla cuál será el valor a la izquierda de la expresión de rendimiento.

Para comprender en qué se diferencia el rendimiento y qué valor tiene, primero actualicemos rápidamente el orden en que se evalúa el código Python.

Sección 6.15 Orden de evaluación

Python evalúa expresiones de izquierda a derecha. Observe que al evaluar una tarea, el lado derecho se evalúa antes que el lado izquierdo.

Entonces, una expresión a = bdel lado derecho se evalúa primero.

Como lo demuestra lo siguiente, a[p('left')] = p('right')el lado derecho se evalúa primero.

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

¿Qué hace el rendimiento?, produce, suspende la ejecución de la función y regresa a la persona que llama, y ​​reanuda la ejecución en el mismo lugar donde la dejó antes de suspender.

¿Dónde exactamente se suspende la ejecución? Quizás ya lo hayas adivinado... la ejecución se suspende entre el lado derecho e izquierdo de la expresión de rendimiento. Entonces, new_val = yield old_valla ejecución se detiene en la =señal, y el valor de la derecha (que es antes de suspender, y también es el valor devuelto a la persona que llama) puede ser algo diferente que el valor de la izquierda (que es el valor que se asigna después de reanudar ejecución).

yieldproduce 2 valores, uno a la derecha y otro a la izquierda.

¿Cómo se controla el valor del lado izquierdo de la expresión de rendimiento? a través del .send()método.

6.2.9. Expresiones de rendimiento

El valor de la expresión de rendimiento después de la reanudación depende del método que reanudó la ejecución. Si __next__()se usa (normalmente a través de un for o el next()incorporado), el resultado es Ninguno. De lo contrario, si send()se usa, el resultado será el valor pasado a ese método.

user2059857 avatar Aug 15 '2018 22:08 user2059857